From 33529f06c4edd37e6061d70da1fb007f929931d4 Mon Sep 17 00:00:00 2001
From: Tk-Glitch <ti3nou@gmail.com>
Date: Mon, 10 Feb 2020 13:03:57 +0100
Subject: Proton fs hack patchset, 5.0 staging rebased edition


diff --git a/dlls/winevulkan/make_vulkan b/dlls/winevulkan/make_vulkan
index 52ef25948c..36b1522001 100755
--- a/dlls/winevulkan/make_vulkan
+++ b/dlls/winevulkan/make_vulkan
@@ -171,6 +171,7 @@ FUNCTION_OVERRIDES = {
     "vkCmdExecuteCommands" : {"dispatch" : True, "driver" : False, "thunk" : False},
     "vkCreateCommandPool" : {"dispatch": True, "driver" : False, "thunk" : False},
     "vkDestroyCommandPool" : {"dispatch": True, "driver" : False, "thunk" : False},
+    "vkCmdPipelineBarrier" : {"dispatch" : True, "driver" : False, "thunk" : False},
     "vkDestroyDevice" : {"dispatch" : True, "driver" : False, "thunk" : False},
     "vkFreeCommandBuffers" : {"dispatch" : True, "driver" : False, "thunk" : False},
     "vkGetDeviceProcAddr" : {"dispatch" : False, "driver" : True, "thunk" : False},
@@ -181,7 +182,7 @@ FUNCTION_OVERRIDES = {
     # VK_KHR_surface
     "vkDestroySurfaceKHR" : {"dispatch" : True, "driver" : True, "thunk" : True},
     "vkGetPhysicalDeviceSurfaceSupportKHR" : {"dispatch" : True, "driver" : True, "thunk" : True},
-    "vkGetPhysicalDeviceSurfaceCapabilitiesKHR" : {"dispatch" : True, "driver" : True, "thunk" : True},
+    "vkGetPhysicalDeviceSurfaceCapabilitiesKHR" : {"dispatch" : True, "driver" : True, "thunk" : False},
     "vkGetPhysicalDeviceSurfaceFormatsKHR" : {"dispatch" : True, "driver" : True, "thunk" : True},
     "vkGetPhysicalDeviceSurfacePresentModesKHR" : {"dispatch" : True, "driver" : True, "thunk" : True},
 
@@ -190,10 +191,11 @@ FUNCTION_OVERRIDES = {
     "vkGetPhysicalDeviceWin32PresentationSupportKHR" : {"dispatch" : True, "driver" : True, "thunk" : True},
 
     # VK_KHR_swapchain
-    "vkCreateSwapchainKHR" : {"dispatch" : True, "driver" : True, "thunk" : True},
-    "vkDestroySwapchainKHR" : {"dispatch" : True, "driver" : True, "thunk" : True},
-    "vkGetSwapchainImagesKHR": {"dispatch" : True, "driver" : True, "thunk" : True},
-    "vkQueuePresentKHR": {"dispatch" : True, "driver" : True, "thunk" : True},
+    "vkAcquireNextImageKHR": {"dispatch" : True, "driver" : False, "thunk" : False},
+    "vkCreateSwapchainKHR" : {"dispatch" : True, "driver" : True, "thunk" : False},
+    "vkDestroySwapchainKHR" : {"dispatch" : True, "driver" : True, "thunk" : False},
+    "vkGetSwapchainImagesKHR": {"dispatch" : True, "driver" : True, "thunk" : False},
+    "vkQueuePresentKHR": {"dispatch" : True, "driver" : True, "thunk" : False},
 
     # VK_KHR_external_fence_capabilities
     "vkGetPhysicalDeviceExternalFencePropertiesKHR" : {"dispatch" : False, "driver" : False, "thunk" : False},
@@ -218,6 +220,7 @@ STRUCT_CHAIN_CONVERSIONS = [
     "VkInstanceCreateInfo",
 ]
 
+shared_conversion_structs = ["VkBufferMemoryBarrier", "VkImageMemoryBarrier"]
 
 class Direction(Enum):
     """ Parameter direction: input, output, input_output. """
@@ -1078,14 +1081,14 @@ class VkMember(object):
         struct = self.type_info["data"]
         direction = Direction.OUTPUT if struct.returnedonly else Direction.INPUT
         if self.is_dynamic_array():
-            conversions.append(ConversionFunction(False, True, direction, struct))
+            conversions.append(ConversionFunction(False, True, struct.name in shared_conversion_structs, direction, struct))
         elif self.is_static_array():
-            conversions.append(ConversionFunction(True, False, direction, struct))
+            conversions.append(ConversionFunction(True, False, struct.name in shared_conversion_structs, direction, struct))
         else:
-            conversions.append(ConversionFunction(False, False, direction, struct))
+            conversions.append(ConversionFunction(False, False, struct.name in shared_conversion_structs, direction, struct))
 
         if self.needs_free():
-            conversions.append(FreeFunction(self.is_dynamic_array(), struct))
+            conversions.append(FreeFunction(self.is_dynamic_array(), struct.name in shared_conversion_structs, struct))
 
         return conversions
 
@@ -1242,16 +1245,16 @@ class VkParam(object):
 
         # Input functions require win to host conversion.
         if self._direction in [Direction.INPUT, Direction.INPUT_OUTPUT]:
-            self.input_conv = ConversionFunction(False, self.is_dynamic_array(), Direction.INPUT, self.struct)
+            self.input_conv = ConversionFunction(False, self.is_dynamic_array(), self.struct.name in shared_conversion_structs, Direction.INPUT, self.struct)
 
         # Output functions require host to win conversion.
         if self._direction in [Direction.INPUT_OUTPUT, Direction.OUTPUT]:
-            self.output_conv = ConversionFunction(False, self.is_dynamic_array(), Direction.OUTPUT, self.struct)
+            self.output_conv = ConversionFunction(False, self.is_dynamic_array(), self.struct.name in shared_conversion_structs, Direction.OUTPUT, self.struct)
 
         # Dynamic arrays, but also some normal structs (e.g. VkCommandBufferBeginInfo) need memory
         # allocation and thus some cleanup.
         if self.is_dynamic_array() or self.struct.needs_free():
-            self.free_func = FreeFunction(self.is_dynamic_array(), self.struct)
+            self.free_func = FreeFunction(self.is_dynamic_array(), self.struct.name in shared_conversion_structs, self.struct)
 
     def _set_direction(self):
         """ Internal helper function to set parameter direction (input/output/input_output). """
@@ -1369,6 +1372,9 @@ class VkParam(object):
 
         return self._direction
 
+    def format_string(self):
+        return self.format_str
+
     def dispatch_table(self):
         """ Return functions dispatch table pointer for dispatchable objects. """
 
@@ -1727,9 +1733,10 @@ class VkStruct(Sequence):
 
 
 class ConversionFunction(object):
-    def __init__(self, array, dyn_array, direction, struct):
+    def __init__(self, array, dyn_array, shared, direction, struct):
         self.array = array
         self.direction = direction
+        self.shared = shared
         self.dyn_array = dyn_array
         self.struct = struct
         self.type = struct.name
@@ -1750,7 +1757,11 @@ class ConversionFunction(object):
             return_type = "{0}_host".format(self.type)
 
         # Generate function prototype.
-        body = "static inline {0} *{1}(".format(return_type, self.name)
+        if self.shared:
+            body = ""
+        else:
+            body = "static inline "
+        body += "{0} *{1}(".format(return_type, self.name)
         body += ", ".join(p for p in params)
         body += ")\n{\n"
 
@@ -1781,7 +1792,11 @@ class ConversionFunction(object):
         else:
             params = ["const {0} *in".format(self.type), "{0}_host *out".format(self.type)]
 
-        body = "static inline void {0}(".format(self.name)
+        if self.shared:
+            body = ""
+        else:
+            body = "static inline "
+        body += "void {0}(".format(self.name)
 
         # Generate parameter list
         body += ", ".join(p for p in params)
@@ -1810,11 +1825,17 @@ class ConversionFunction(object):
 
         if self.direction == Direction.OUTPUT:
             params = ["const {0}_host *in".format(self.type), "{0} *out".format(self.type), "uint32_t count"]
+            return_type = self.type
         else:
             params = ["const {0} *in".format(self.type), "{0} *out_host".format(self.type), "uint32_t count"]
+            return_type = "{0}_host".format(self.type)
 
         # Generate function prototype.
-        body = "static inline void {0}(".format(self.name)
+        if self.shared:
+            body = ""
+        else:
+            body = "static inline "
+        body += "void {0}(".format(self.name)
         body += ", ".join(p for p in params)
         body += ")\n{\n"
         body += "    unsigned int i;\n\n"
@@ -1856,10 +1877,46 @@ class ConversionFunction(object):
         else:
             return self._generate_conversion_func()
 
+    def prototype(self):
+        if self.array:
+            if self.direction == Direction.OUTPUT:
+                params = ["const {0}_host *in".format(self.type), "{0} *out".format(self.type), "uint32_t count"]
+                return_type = self.type
+            else:
+                params = ["const {0} *in".format(self.type), "{0} *out_host".format(self.type), "uint32_t count"]
+                return_type = "{0}_host".format(self.type)
+
+            body = "void {0}(".format(self.name)
+            body += ", ".join(p for p in params)
+            body += ");\n"
+        elif self.dyn_array:
+            if self.direction == Direction.OUTPUT:
+                params = ["const {0}_host *in".format(self.type), "uint32_t count"]
+                return_type = self.type
+            else:
+                params = ["const {0} *in".format(self.type), "uint32_t count"]
+                return_type = "{0}_host".format(self.type)
+
+            body = "{0} *{1}(".format(return_type, self.name)
+            body += ", ".join(p for p in params)
+            body += ");\n"
+        else:
+            if self.direction == Direction.OUTPUT:
+                params = ["const {0}_host *in".format(self.type), "{0} *out".format(self.type)]
+            else:
+                params = ["const {0} *in".format(self.type), "{0}_host *out".format(self.type)]
+
+            body = "void {0}(".format(self.name)
+            body += ", ".join(p for p in params)
+            body += ");\n"
+
+        return body
+
 
 class FreeFunction(object):
-    def __init__(self, dyn_array, struct):
+    def __init__(self, dyn_array, shared, struct):
         self.dyn_array = dyn_array
+        self.shared = shared
         self.struct = struct
         self.type = struct.name
 
@@ -1875,7 +1932,11 @@ class FreeFunction(object):
         """ Helper function for cleaning up temporary buffers required for array conversions. """
 
         # Generate function prototype.
-        body = "static inline void {0}({1}_host *in, uint32_t count)\n{{\n".format(self.name, self.type)
+        if self.shared:
+            body = ""
+        else:
+            body = "static inline "
+        body += "void {0}({1}_host *in, uint32_t count)\n{{\n".format(self.name, self.type)
 
         # E.g. VkGraphicsPipelineCreateInfo_host needs freeing for pStages.
         if self.struct.needs_free():
@@ -1908,7 +1969,11 @@ class FreeFunction(object):
             return ""
 
         # Generate function prototype.
-        body = "static inline void {0}({1}_host *in)\n{{\n".format(self.name, self.type)
+        if self.shared:
+            body = ""
+        else:
+            body = "static inline "
+        body += "void {0}({1}_host *in)\n{{\n".format(self.name, self.type)
 
         for m in self.struct:
             if m.needs_conversion() and m.is_dynamic_array():
@@ -1930,6 +1995,11 @@ class FreeFunction(object):
             # E.g. VkCommandBufferBeginInfo
             return self._generate_free_func()
 
+    def prototype(self):
+        if self.dyn_array:
+            return "void {0}({1}_host *in, uint32_t count);\n".format(self.name, self.type)
+        return "void {0}({1}_host *in);\n".format(self.name, self.type)
+
 
 class StructChainConversionFunction(object):
     def __init__(self, direction, struct):
@@ -2092,9 +2162,14 @@ class VkGenerator(object):
         # Generate any conversion helper functions.
         f.write("#if defined(USE_STRUCT_CONVERSION)\n")
         for conv in self.conversions:
-            f.write(conv.definition())
+            if not conv.shared:
+                f.write(conv.definition())
         f.write("#endif /* USE_STRUCT_CONVERSION */\n\n")
 
+        for conv in self.conversions:
+            if conv.shared:
+                f.write(conv.definition())
+
         for conv in self.struct_chain_conversions:
             f.write(conv.definition())
 
@@ -2239,6 +2314,10 @@ class VkGenerator(object):
             f.write(func.prototype(postfix="DECLSPEC_HIDDEN") + ";\n")
         f.write("\n")
 
+        for conv in self.conversions:
+            if conv.shared:
+                f.write(conv.prototype())
+
         f.write("/* For use by vkDevice and children */\n")
         f.write("struct vulkan_device_funcs\n{\n")
         for vk_func in self.registry.device_funcs:
@@ -2432,6 +2511,14 @@ class VkGenerator(object):
             # stuff in there. For simplicity substitute with "void *".
             pfn = pfn.replace("PFN_vkVoidFunction", "void *")
             f.write("    {0};\n".format(pfn))
+
+        f.write("\n    /* Optional. Returns TRUE if FS hack is active, otherwise returns FALSE. If\n")
+        f.write("     * it returns TRUE, then real_sz will contain the actual display\n")
+        f.write("     * resolution; user_sz will contain the app's requested mode; and dst_blit\n")
+        f.write("     * will contain the area to blit the user image to in real coordinates.\n")
+        f.write("     * All parameters are optional. */\n")
+        f.write("    VkBool32 (*query_fs_hack)(VkExtent2D *real_sz, VkExtent2D *user_sz, VkRect2D *dst_blit);\n")
+
         f.write("};\n\n")
 
         f.write("extern const struct vulkan_funcs * CDECL __wine_get_vulkan_driver(HDC hdc, UINT version);\n\n")
@@ -2472,6 +2559,11 @@ class VkGenerator(object):
         f.write("@ stdcall -private vk_icdGetInstanceProcAddr(ptr str) wine_vk_icdGetInstanceProcAddr\n")
         f.write("@ stdcall -private vk_icdNegotiateLoaderICDInterfaceVersion(ptr) wine_vk_icdNegotiateLoaderICDInterfaceVersion\n")
         f.write("@ cdecl -norelay native_vkGetInstanceProcAddrWINE(ptr str)\n")
+        f.write("@ stdcall __wine_get_native_VkDevice(ptr)\n")
+        f.write("@ stdcall __wine_get_native_VkInstance(ptr)\n")
+        f.write("@ stdcall __wine_get_native_VkPhysicalDevice(ptr)\n")
+        f.write("@ stdcall __wine_get_wrapped_VkPhysicalDevice(ptr)\n")
+        f.write("@ stdcall __wine_get_native_VkQueue(ptr)\n")
 
         # Export symbols for all Vulkan Core functions.
         for func in self.registry.funcs.values():
diff --git a/dlls/winevulkan/vulkan.c b/dlls/winevulkan/vulkan.c
index 59472bcef8..3ade532f0c 100644
--- a/dlls/winevulkan/vulkan.c
+++ b/dlls/winevulkan/vulkan.c
@@ -18,6 +18,7 @@
  */
 
 #include <stdarg.h>
+#include <math.h>
 
 #include "windef.h"
 #include "winbase.h"
@@ -295,6 +296,10 @@ static void wine_vk_device_free(struct VkDevice_T *device)
         device->funcs.p_vkDestroyDevice(device->device, NULL /* pAllocator */);
     }
 
+    heap_free(device->queue_props);
+    heap_free(device->swapchains);
+    DeleteCriticalSection(&device->swapchain_lock);
+
     heap_free(device);
 }
 
@@ -583,6 +588,8 @@ VkResult WINAPI wine_vkCreateDevice(VkPhysicalDevice phys_dev,
         goto fail;
     }
 
+    object->phys_dev = phys_dev;
+
     /* Just load all function pointers we are aware off. The loader takes care of filtering.
      * We use vkGetDeviceProcAddr as opposed to vkGetInstanceProcAddr for efficiency reasons
      * as functions pass through fewer dispatch tables within the loader.
@@ -626,6 +633,8 @@ VkResult WINAPI wine_vkCreateDevice(VkPhysicalDevice phys_dev,
 
     object->quirks = phys_dev->instance->quirks;
 
+    InitializeCriticalSection(&object->swapchain_lock);
+
     *device = object;
     TRACE("Created device %p (native device %p).\n", object, object->device);
     return VK_SUCCESS;
@@ -1307,3 +1316,1469 @@ void *native_vkGetInstanceProcAddrWINE(VkInstance instance, const char *name)
 {
     return vk_funcs->p_vkGetInstanceProcAddr(instance, name);
 }
+
+VkResult WINAPI wine_vkGetPhysicalDeviceSurfaceCapabilitiesKHR(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, VkSurfaceCapabilitiesKHR *pSurfaceCapabilities)
+{
+    VkResult res;
+    VkExtent2D user_res;
+
+    TRACE("%p, 0x%s, %p\n", physicalDevice, wine_dbgstr_longlong(surface), pSurfaceCapabilities);
+
+    res = physicalDevice->instance->funcs.p_vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physicalDevice->phys_dev, surface, pSurfaceCapabilities);
+    if(res != VK_SUCCESS)
+        return res;
+
+    if(vk_funcs->query_fs_hack &&
+            vk_funcs->query_fs_hack(NULL, &user_res, NULL)){
+        pSurfaceCapabilities->currentExtent = user_res;
+        pSurfaceCapabilities->minImageExtent = user_res;
+        pSurfaceCapabilities->maxImageExtent = user_res;
+    }
+
+    return VK_SUCCESS;
+}
+
+VkResult WINAPI wine_vkAcquireNextImageKHR(VkDevice device, VkSwapchainKHR swapchain, uint64_t timeout, VkSemaphore semaphore, VkFence fence, uint32_t *pImageIndex)
+{
+    struct VkSwapchainKHR_T *object = (struct VkSwapchainKHR_T *)(UINT_PTR)swapchain;
+    TRACE("%p, 0x%s, 0x%s, 0x%s, 0x%s, %p\n", device, wine_dbgstr_longlong(swapchain), wine_dbgstr_longlong(timeout), wine_dbgstr_longlong(semaphore), wine_dbgstr_longlong(fence), pImageIndex);
+    return device->funcs.p_vkAcquireNextImageKHR(device->device, object->swapchain, timeout, semaphore, fence, pImageIndex);
+}
+
+#if defined(USE_STRUCT_CONVERSION)
+static inline void convert_VkSwapchainCreateInfoKHR_win_to_host(const VkSwapchainCreateInfoKHR *in, VkSwapchainCreateInfoKHR_host *out)
+#else
+static inline void convert_VkSwapchainCreateInfoKHR_win_to_host(const VkSwapchainCreateInfoKHR *in, VkSwapchainCreateInfoKHR *out)
+#endif
+{
+    if (!in) return;
+
+    out->sType = in->sType;
+    out->pNext = in->pNext;
+    out->flags = in->flags;
+    out->surface = in->surface;
+    out->minImageCount = in->minImageCount;
+    out->imageFormat = in->imageFormat;
+    out->imageColorSpace = in->imageColorSpace;
+    out->imageExtent = in->imageExtent;
+    out->imageArrayLayers = in->imageArrayLayers;
+    out->imageUsage = in->imageUsage;
+    out->imageSharingMode = in->imageSharingMode;
+    out->queueFamilyIndexCount = in->queueFamilyIndexCount;
+    out->pQueueFamilyIndices = in->pQueueFamilyIndices;
+    out->preTransform = in->preTransform;
+    out->compositeAlpha = in->compositeAlpha;
+    out->presentMode = in->presentMode;
+    out->clipped = in->clipped;
+    out->oldSwapchain = in->oldSwapchain;
+}
+
+/*
+#version 450
+
+layout(binding = 0) uniform sampler2D texSampler;
+layout(binding = 1, rgba8) uniform writeonly image2D outImage;
+layout(push_constant) uniform pushConstants {
+    //both in real image coords
+    vec2 offset;
+    vec2 extents;
+} constants;
+
+layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
+
+void main()
+{
+    vec2 texcoord = (vec2(gl_GlobalInvocationID.xy) - constants.offset) / constants.extents;
+    vec4 c = texture(texSampler, texcoord);
+    imageStore(outImage, ivec2(gl_GlobalInvocationID.xy), c.bgra);
+}
+*/
+const uint32_t blit_comp_spv[] = {
+	0x07230203,0x00010000,0x00080006,0x00000037,0x00000000,0x00020011,0x00000001,0x0006000b,
+	0x00000001,0x4c534c47,0x6474732e,0x3035342e,0x00000000,0x0003000e,0x00000000,0x00000001,
+	0x0006000f,0x00000005,0x00000004,0x6e69616d,0x00000000,0x0000000d,0x00060010,0x00000004,
+	0x00000011,0x00000008,0x00000008,0x00000001,0x00030003,0x00000002,0x000001c2,0x00040005,
+	0x00000004,0x6e69616d,0x00000000,0x00050005,0x00000009,0x63786574,0x64726f6f,0x00000000,
+	0x00080005,0x0000000d,0x475f6c67,0x61626f6c,0x766e496c,0x7461636f,0x496e6f69,0x00000044,
+	0x00060005,0x00000012,0x68737570,0x736e6f43,0x746e6174,0x00000073,0x00050006,0x00000012,
+	0x00000000,0x7366666f,0x00007465,0x00050006,0x00000012,0x00000001,0x65747865,0x0073746e,
+	0x00050005,0x00000014,0x736e6f63,0x746e6174,0x00000073,0x00030005,0x00000021,0x00000063,
+	0x00050005,0x00000025,0x53786574,0x6c706d61,0x00007265,0x00050005,0x0000002c,0x4974756f,
+	0x6567616d,0x00000000,0x00040047,0x0000000d,0x0000000b,0x0000001c,0x00050048,0x00000012,
+	0x00000000,0x00000023,0x00000000,0x00050048,0x00000012,0x00000001,0x00000023,0x00000008,
+	0x00030047,0x00000012,0x00000002,0x00040047,0x00000025,0x00000022,0x00000000,0x00040047,
+	0x00000025,0x00000021,0x00000000,0x00040047,0x0000002c,0x00000022,0x00000000,0x00040047,
+	0x0000002c,0x00000021,0x00000001,0x00030047,0x0000002c,0x00000019,0x00040047,0x00000036,
+	0x0000000b,0x00000019,0x00020013,0x00000002,0x00030021,0x00000003,0x00000002,0x00030016,
+	0x00000006,0x00000020,0x00040017,0x00000007,0x00000006,0x00000002,0x00040020,0x00000008,
+	0x00000007,0x00000007,0x00040015,0x0000000a,0x00000020,0x00000000,0x00040017,0x0000000b,
+	0x0000000a,0x00000003,0x00040020,0x0000000c,0x00000001,0x0000000b,0x0004003b,0x0000000c,
+	0x0000000d,0x00000001,0x00040017,0x0000000e,0x0000000a,0x00000002,0x0004001e,0x00000012,
+	0x00000007,0x00000007,0x00040020,0x00000013,0x00000009,0x00000012,0x0004003b,0x00000013,
+	0x00000014,0x00000009,0x00040015,0x00000015,0x00000020,0x00000001,0x0004002b,0x00000015,
+	0x00000016,0x00000000,0x00040020,0x00000017,0x00000009,0x00000007,0x0004002b,0x00000015,
+	0x0000001b,0x00000001,0x00040017,0x0000001f,0x00000006,0x00000004,0x00040020,0x00000020,
+	0x00000007,0x0000001f,0x00090019,0x00000022,0x00000006,0x00000001,0x00000000,0x00000000,
+	0x00000000,0x00000001,0x00000000,0x0003001b,0x00000023,0x00000022,0x00040020,0x00000024,
+	0x00000000,0x00000023,0x0004003b,0x00000024,0x00000025,0x00000000,0x0004002b,0x00000006,
+	0x00000028,0x00000000,0x00090019,0x0000002a,0x00000006,0x00000001,0x00000000,0x00000000,
+	0x00000000,0x00000002,0x00000004,0x00040020,0x0000002b,0x00000000,0x0000002a,0x0004003b,
+	0x0000002b,0x0000002c,0x00000000,0x00040017,0x00000030,0x00000015,0x00000002,0x0004002b,
+	0x0000000a,0x00000034,0x00000008,0x0004002b,0x0000000a,0x00000035,0x00000001,0x0006002c,
+	0x0000000b,0x00000036,0x00000034,0x00000034,0x00000035,0x00050036,0x00000002,0x00000004,
+	0x00000000,0x00000003,0x000200f8,0x00000005,0x0004003b,0x00000008,0x00000009,0x00000007,
+	0x0004003b,0x00000020,0x00000021,0x00000007,0x0004003d,0x0000000b,0x0000000f,0x0000000d,
+	0x0007004f,0x0000000e,0x00000010,0x0000000f,0x0000000f,0x00000000,0x00000001,0x00040070,
+	0x00000007,0x00000011,0x00000010,0x00050041,0x00000017,0x00000018,0x00000014,0x00000016,
+	0x0004003d,0x00000007,0x00000019,0x00000018,0x00050083,0x00000007,0x0000001a,0x00000011,
+	0x00000019,0x00050041,0x00000017,0x0000001c,0x00000014,0x0000001b,0x0004003d,0x00000007,
+	0x0000001d,0x0000001c,0x00050088,0x00000007,0x0000001e,0x0000001a,0x0000001d,0x0003003e,
+	0x00000009,0x0000001e,0x0004003d,0x00000023,0x00000026,0x00000025,0x0004003d,0x00000007,
+	0x00000027,0x00000009,0x00070058,0x0000001f,0x00000029,0x00000026,0x00000027,0x00000002,
+	0x00000028,0x0003003e,0x00000021,0x00000029,0x0004003d,0x0000002a,0x0000002d,0x0000002c,
+	0x0004003d,0x0000000b,0x0000002e,0x0000000d,0x0007004f,0x0000000e,0x0000002f,0x0000002e,
+	0x0000002e,0x00000000,0x00000001,0x0004007c,0x00000030,0x00000031,0x0000002f,0x0004003d,
+	0x0000001f,0x00000032,0x00000021,0x0009004f,0x0000001f,0x00000033,0x00000032,0x00000032,
+	0x00000002,0x00000001,0x00000000,0x00000003,0x00040063,0x0000002d,0x00000031,0x00000033,
+	0x000100fd,0x00010038
+};
+
+static VkResult create_pipeline(VkDevice device, struct VkSwapchainKHR_T *swapchain, struct fs_hack_image *hack, VkShaderModule shaderModule)
+{
+    VkResult res;
+#if defined(USE_STRUCT_CONVERSION)
+    VkComputePipelineCreateInfo_host pipelineInfo = {0};
+#else
+    VkComputePipelineCreateInfo pipelineInfo = {0};
+#endif
+
+    pipelineInfo.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO;
+    pipelineInfo.stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+    pipelineInfo.stage.stage = VK_SHADER_STAGE_COMPUTE_BIT;
+    pipelineInfo.stage.module = shaderModule;
+    pipelineInfo.stage.pName = "main";
+    pipelineInfo.layout = swapchain->pipeline_layout;
+    pipelineInfo.basePipelineHandle = VK_NULL_HANDLE;
+    pipelineInfo.basePipelineIndex = -1;
+
+    res = device->funcs.p_vkCreateComputePipelines(device->device, VK_NULL_HANDLE, 1, &pipelineInfo, NULL, &hack->pipeline);
+    if(res != VK_SUCCESS){
+        ERR("vkCreateComputePipelines: %d\n", res);
+        return res;
+    }
+
+    return VK_SUCCESS;
+}
+
+static VkResult create_descriptor_set(VkDevice device, struct VkSwapchainKHR_T *swapchain, struct fs_hack_image *hack)
+{
+    VkResult res;
+#if defined(USE_STRUCT_CONVERSION)
+    VkDescriptorSetAllocateInfo_host descriptorAllocInfo = {0};
+    VkWriteDescriptorSet_host descriptorWrites[2] = {{0}, {0}};
+    VkDescriptorImageInfo_host userDescriptorImageInfo = {0}, realDescriptorImageInfo = {0};
+#else
+    VkDescriptorSetAllocateInfo descriptorAllocInfo = {0};
+    VkWriteDescriptorSet descriptorWrites[2] = {{0}, {0}};
+    VkDescriptorImageInfo userDescriptorImageInfo = {0}, realDescriptorImageInfo = {0};
+#endif
+
+    descriptorAllocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
+    descriptorAllocInfo.descriptorPool = swapchain->descriptor_pool;
+    descriptorAllocInfo.descriptorSetCount = 1;
+    descriptorAllocInfo.pSetLayouts = &swapchain->descriptor_set_layout;
+
+    res = device->funcs.p_vkAllocateDescriptorSets(device->device, &descriptorAllocInfo, &hack->descriptor_set);
+    if(res != VK_SUCCESS){
+        ERR("vkAllocateDescriptorSets: %d\n", res);
+        return res;
+    }
+
+    userDescriptorImageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+    userDescriptorImageInfo.imageView = hack->user_view;
+    userDescriptorImageInfo.sampler = swapchain->sampler;
+
+    realDescriptorImageInfo.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
+    realDescriptorImageInfo.imageView = hack->blit_view;
+
+    descriptorWrites[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+    descriptorWrites[0].dstSet = hack->descriptor_set;
+    descriptorWrites[0].dstBinding = 0;
+    descriptorWrites[0].dstArrayElement = 0;
+    descriptorWrites[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+    descriptorWrites[0].descriptorCount = 1;
+    descriptorWrites[0].pImageInfo = &userDescriptorImageInfo;
+
+    descriptorWrites[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+    descriptorWrites[1].dstSet = hack->descriptor_set;
+    descriptorWrites[1].dstBinding = 1;
+    descriptorWrites[1].dstArrayElement = 0;
+    descriptorWrites[1].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
+    descriptorWrites[1].descriptorCount = 1;
+    descriptorWrites[1].pImageInfo = &realDescriptorImageInfo;
+
+    device->funcs.p_vkUpdateDescriptorSets(device->device, 2, descriptorWrites, 0, NULL);
+
+    return VK_SUCCESS;
+}
+
+static void destroy_fs_hack_image(VkDevice device, struct VkSwapchainKHR_T *swapchain, struct fs_hack_image *hack)
+{
+    device->funcs.p_vkDestroyPipeline(device->device, hack->pipeline, NULL);
+    device->funcs.p_vkFreeDescriptorSets(device->device, swapchain->descriptor_pool, 1, &hack->descriptor_set);
+    device->funcs.p_vkDestroyImageView(device->device, hack->user_view, NULL);
+    device->funcs.p_vkDestroyImageView(device->device, hack->blit_view, NULL);
+    device->funcs.p_vkDestroyImage(device->device, hack->user_image, NULL);
+    device->funcs.p_vkDestroyImage(device->device, hack->blit_image, NULL);
+    if(hack->cmd)
+        device->funcs.p_vkFreeCommandBuffers(device->device,
+                swapchain->cmd_pools[hack->cmd_queue_idx],
+                    1, &hack->cmd);
+    device->funcs.p_vkDestroySemaphore(device->device, hack->blit_finished, NULL);
+}
+
+#if defined(USE_STRUCT_CONVERSION)
+static VkResult init_fs_hack_images(VkDevice device, struct VkSwapchainKHR_T *swapchain, VkSwapchainCreateInfoKHR_host *createinfo)
+#else
+static VkResult init_fs_hack_images(VkDevice device, struct VkSwapchainKHR_T *swapchain, VkSwapchainCreateInfoKHR *createinfo)
+#endif
+{
+    VkResult res;
+    VkImage *real_images = NULL;
+    VkDeviceSize userMemTotal = 0, offs;
+    VkImageCreateInfo imageInfo = {0};
+    VkSemaphoreCreateInfo semaphoreInfo = {0};
+#if defined(USE_STRUCT_CONVERSION)
+    VkMemoryRequirements_host userMemReq;
+    VkMemoryAllocateInfo_host allocInfo = {0};
+    VkPhysicalDeviceMemoryProperties_host memProperties;
+    VkImageViewCreateInfo_host viewInfo = {0};
+#else
+    VkMemoryRequirements userMemReq;
+    VkMemoryAllocateInfo allocInfo = {0};
+    VkPhysicalDeviceMemoryProperties memProperties;
+    VkImageViewCreateInfo viewInfo = {0};
+#endif
+    uint32_t count, i = 0, user_memory_type = -1;
+
+    res = device->funcs.p_vkGetSwapchainImagesKHR(device->device, swapchain->swapchain, &count, NULL);
+    if(res != VK_SUCCESS)
+    {
+        WARN("vkGetSwapchainImagesKHR failed, res=%d\n", res);
+        return res;
+    }
+
+    real_images = heap_alloc(count * sizeof(VkImage));
+    swapchain->cmd_pools = heap_alloc_zero(sizeof(VkCommandPool) * device->max_queue_families);
+    swapchain->fs_hack_images = heap_alloc_zero(sizeof(struct fs_hack_image) * count);
+    if(!real_images || !swapchain->cmd_pools || !swapchain->fs_hack_images)
+        goto fail;
+
+    res = device->funcs.p_vkGetSwapchainImagesKHR(device->device, swapchain->swapchain, &count, real_images);
+    if(res != VK_SUCCESS)
+    {
+        WARN("vkGetSwapchainImagesKHR failed, res=%d\n", res);
+        goto fail;
+    }
+
+    /* create user images */
+    for(i = 0; i < count; ++i){
+        struct fs_hack_image *hack = &swapchain->fs_hack_images[i];
+
+        hack->swapchain_image = real_images[i];
+
+        semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
+        res = device->funcs.p_vkCreateSemaphore(device->device, &semaphoreInfo, NULL, &hack->blit_finished);
+        if(res != VK_SUCCESS)
+        {
+            WARN("vkCreateSemaphore failed, res=%d\n", res);
+            goto fail;
+        }
+
+        imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
+        imageInfo.imageType = VK_IMAGE_TYPE_2D;
+        imageInfo.extent.width = swapchain->user_extent.width;
+        imageInfo.extent.height = swapchain->user_extent.height;
+        imageInfo.extent.depth = 1;
+        imageInfo.mipLevels = 1;
+        imageInfo.arrayLayers = createinfo->imageArrayLayers;
+        imageInfo.format = createinfo->imageFormat;
+        imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
+        imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+        imageInfo.usage = createinfo->imageUsage | VK_IMAGE_USAGE_SAMPLED_BIT;
+        imageInfo.sharingMode = createinfo->imageSharingMode;
+        imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
+        imageInfo.queueFamilyIndexCount = createinfo->queueFamilyIndexCount;
+        imageInfo.pQueueFamilyIndices = createinfo->pQueueFamilyIndices;
+        res = device->funcs.p_vkCreateImage(device->device, &imageInfo, NULL, &hack->user_image);
+        if(res != VK_SUCCESS){
+            ERR("vkCreateImage failed: %d\n", res);
+            goto fail;
+        }
+
+        device->funcs.p_vkGetImageMemoryRequirements(device->device, hack->user_image, &userMemReq);
+
+        offs = userMemTotal % userMemReq.alignment;
+        if(offs)
+            userMemTotal += userMemReq.alignment - offs;
+
+        userMemTotal += userMemReq.size;
+
+        swapchain->n_images++;
+    }
+
+    /* allocate backing memory */
+    device->phys_dev->instance->funcs.p_vkGetPhysicalDeviceMemoryProperties(device->phys_dev->phys_dev, &memProperties);
+
+    for (i = 0; i < memProperties.memoryTypeCount; i++){
+        if((memProperties.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) == VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT){
+            if(userMemReq.memoryTypeBits & (1 << i)){
+                user_memory_type = i;
+                break;
+            }
+        }
+    }
+
+    if(user_memory_type == -1){
+        ERR("unable to find suitable memory type\n");
+        res = VK_ERROR_OUT_OF_HOST_MEMORY;
+        goto fail;
+    }
+
+    allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
+    allocInfo.allocationSize = userMemTotal;
+    allocInfo.memoryTypeIndex = user_memory_type;
+
+    res = device->funcs.p_vkAllocateMemory(device->device, &allocInfo, NULL, &swapchain->user_image_memory);
+    if(res != VK_SUCCESS){
+        ERR("vkAllocateMemory: %d\n", res);
+        goto fail;
+    }
+
+    /* bind backing memory and create imageviews */
+    userMemTotal = 0;
+    for(i = 0; i < count; ++i){
+        device->funcs.p_vkGetImageMemoryRequirements(device->device, swapchain->fs_hack_images[i].user_image, &userMemReq);
+
+        offs = userMemTotal % userMemReq.alignment;
+        if(offs)
+            userMemTotal += userMemReq.alignment - offs;
+
+        res = device->funcs.p_vkBindImageMemory(device->device, swapchain->fs_hack_images[i].user_image, swapchain->user_image_memory, userMemTotal);
+        if(res != VK_SUCCESS){
+            ERR("vkBindImageMemory: %d\n", res);
+            goto fail;
+        }
+
+        userMemTotal += userMemReq.size;
+
+        viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
+        viewInfo.image = swapchain->fs_hack_images[i].user_image;
+        viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
+        viewInfo.format = createinfo->imageFormat;
+        viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+        viewInfo.subresourceRange.baseMipLevel = 0;
+        viewInfo.subresourceRange.levelCount = 1;
+        viewInfo.subresourceRange.baseArrayLayer = 0;
+        viewInfo.subresourceRange.layerCount = 1;
+
+        res = device->funcs.p_vkCreateImageView(device->device, &viewInfo, NULL, &swapchain->fs_hack_images[i].user_view);
+        if(res != VK_SUCCESS){
+            ERR("vkCreateImageView(user): %d\n", res);
+            goto fail;
+        }
+    }
+
+    heap_free(real_images);
+
+    return VK_SUCCESS;
+
+fail:
+    for(i = 0; i < swapchain->n_images; ++i)
+        destroy_fs_hack_image(device, swapchain, &swapchain->fs_hack_images[i]);
+    heap_free(real_images);
+    heap_free(swapchain->cmd_pools);
+    heap_free(swapchain->fs_hack_images);
+    return res;
+}
+
+static VkResult init_blit_images(VkDevice device, struct VkSwapchainKHR_T *swapchain);
+VkResult WINAPI wine_vkCreateSwapchainKHR(VkDevice device, const VkSwapchainCreateInfoKHR *pCreateInfo, const VkAllocationCallbacks *pAllocator, VkSwapchainKHR *pSwapchain)
+{
+    VkResult result;
+#if defined(USE_STRUCT_CONVERSION)
+    VkSwapchainCreateInfoKHR_host our_createinfo;
+#else
+    VkSwapchainCreateInfoKHR our_createinfo;
+#endif
+    VkExtent2D user_sz;
+    struct VkSwapchainKHR_T *object;
+    uint32_t i;
+
+    TRACE("%p, %p, %p, %p\n", device, pCreateInfo, pAllocator, pSwapchain);
+
+    if (!(object = heap_alloc_zero(sizeof(*object))))
+    {
+        ERR("Failed to allocate memory for swapchain\n");
+        return VK_ERROR_OUT_OF_HOST_MEMORY;
+    }
+    object->base.loader_magic = VULKAN_ICD_MAGIC_VALUE;
+
+    convert_VkSwapchainCreateInfoKHR_win_to_host(pCreateInfo, &our_createinfo);
+
+    if(our_createinfo.oldSwapchain)
+        our_createinfo.oldSwapchain = ((struct VkSwapchainKHR_T *)(UINT_PTR)our_createinfo.oldSwapchain)->swapchain;
+
+    if(vk_funcs->query_fs_hack &&
+            vk_funcs->query_fs_hack(&object->real_extent, &user_sz, &object->blit_dst) &&
+            our_createinfo.imageExtent.width == user_sz.width &&
+            our_createinfo.imageExtent.height == user_sz.height)
+    {
+        uint32_t count;
+        VkSurfaceCapabilitiesKHR caps = {0};
+
+        device->phys_dev->instance->funcs.p_vkGetPhysicalDeviceQueueFamilyProperties(device->phys_dev->phys_dev, &count, NULL);
+
+        device->queue_props = heap_alloc(sizeof(VkQueueFamilyProperties) * count);
+
+        device->phys_dev->instance->funcs.p_vkGetPhysicalDeviceQueueFamilyProperties(device->phys_dev->phys_dev, &count, device->queue_props);
+
+        result = device->phys_dev->instance->funcs.p_vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device->phys_dev->phys_dev, pCreateInfo->surface, &caps);
+        if(result != VK_SUCCESS)
+        {
+            TRACE("vkGetPhysicalDeviceSurfaceCapabilities failed, res=%d\n", result);
+            heap_free(object);
+            return result;
+        }
+
+        object->surface_usage = caps.supportedUsageFlags;
+        TRACE("surface usage flags: 0x%x\n", object->surface_usage);
+
+        our_createinfo.imageExtent = object->real_extent;
+        our_createinfo.imageUsage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT; /* XXX: check if supported by surface */
+
+        if(our_createinfo.imageFormat != VK_FORMAT_B8G8R8A8_UNORM &&
+                our_createinfo.imageFormat != VK_FORMAT_B8G8R8A8_SRGB){
+            FIXME("swapchain image format is not BGRA8 UNORM/SRGB. Things may go badly. %d\n", our_createinfo.imageFormat);
+        }
+
+        object->fs_hack_enabled = TRUE;
+    }
+
+    result = device->funcs.p_vkCreateSwapchainKHR(device->device, &our_createinfo, NULL, &object->swapchain);
+    if(result != VK_SUCCESS)
+    {
+        TRACE("vkCreateSwapchainKHR failed, res=%d\n", result);
+        heap_free(object);
+        return result;
+    }
+
+    if(object->fs_hack_enabled){
+        object->user_extent = pCreateInfo->imageExtent;
+
+        result = init_fs_hack_images(device, object, &our_createinfo);
+        if(result != VK_SUCCESS){
+            ERR("creating fs hack images failed: %d\n", result);
+            device->funcs.p_vkDestroySwapchainKHR(device->device, object->swapchain, NULL);
+            heap_free(object);
+            return result;
+        }
+
+        /* FIXME: would be nice to do this on-demand, but games can use up all
+         * memory so we fail to allocate later */
+        result = init_blit_images(device, object);
+        if(result != VK_SUCCESS){
+            ERR("creating blit images failed: %d\n", result);
+            wine_vkDestroySwapchainKHR(device, (VkSwapchainKHR)object, NULL);
+            return result;
+        }
+    }
+
+    if(result != VK_SUCCESS){
+        heap_free(object);
+        return result;
+    }
+
+    EnterCriticalSection(&device->swapchain_lock);
+    for(i = 0; i < device->num_swapchains; ++i){
+        if(!device->swapchains[i]){
+            device->swapchains[i] = object;
+            break;
+        }
+    }
+    if(i == device->num_swapchains){
+        struct VkSwapchainKHR_T **swapchains;
+        swapchains = heap_realloc(device->swapchains, sizeof(struct VkSwapchainKHR_T *) * (device->num_swapchains + 1));
+        if(!swapchains){
+            device->funcs.p_vkDestroySwapchainKHR(device->device, object->swapchain, NULL);
+            heap_free(object);
+            return VK_ERROR_OUT_OF_HOST_MEMORY;
+        }
+        swapchains[i] = object;
+        device->swapchains = swapchains;
+        device->num_swapchains += 1;
+    }
+    LeaveCriticalSection(&device->swapchain_lock);
+
+    *pSwapchain = (uint64_t)(UINT_PTR)object;
+
+    return result;
+}
+
+void WINAPI wine_vkDestroySwapchainKHR(VkDevice device, VkSwapchainKHR swapchain, const VkAllocationCallbacks *pAllocator)
+{
+    struct VkSwapchainKHR_T *object = (struct VkSwapchainKHR_T *)(UINT_PTR)swapchain;
+    uint32_t i;
+
+    TRACE("%p, 0x%s, %p\n", device, wine_dbgstr_longlong(swapchain), pAllocator);
+
+    if(!object)
+        return;
+
+    EnterCriticalSection(&device->swapchain_lock);
+    for(i = 0; i < device->num_swapchains; ++i){
+        if(device->swapchains[i] == object){
+            device->swapchains[i] = NULL;
+            break;
+        }
+    }
+    LeaveCriticalSection(&device->swapchain_lock);
+
+    if(object->fs_hack_enabled){
+        for(i = 0; i < object->n_images; ++i)
+            destroy_fs_hack_image(device, object, &object->fs_hack_images[i]);
+
+        for(i = 0; i < device->max_queue_families; ++i)
+            if(object->cmd_pools[i])
+                device->funcs.p_vkDestroyCommandPool(device->device, object->cmd_pools[i], NULL);
+
+        device->funcs.p_vkDestroyPipelineLayout(device->device, object->pipeline_layout, NULL);
+        device->funcs.p_vkDestroyDescriptorSetLayout(device->device, object->descriptor_set_layout, NULL);
+        device->funcs.p_vkDestroyDescriptorPool(device->device, object->descriptor_pool, NULL);
+        device->funcs.p_vkDestroySampler(device->device, object->sampler, NULL);
+        device->funcs.p_vkFreeMemory(device->device, object->user_image_memory, NULL);
+        device->funcs.p_vkFreeMemory(device->device, object->blit_image_memory, NULL);
+        heap_free(object->cmd_pools);
+        heap_free(object->fs_hack_images);
+    }
+
+    device->funcs.p_vkDestroySwapchainKHR(device->device, object->swapchain, NULL);
+
+    heap_free(object);
+}
+
+VkResult WINAPI wine_vkGetSwapchainImagesKHR(VkDevice device, VkSwapchainKHR swapchain, uint32_t *pSwapchainImageCount, VkImage *pSwapchainImages)
+{
+    struct VkSwapchainKHR_T *object = (struct VkSwapchainKHR_T *)(UINT_PTR)swapchain;
+    uint32_t i;
+
+    TRACE("%p, 0x%s, %p, %p\n", device, wine_dbgstr_longlong(swapchain), pSwapchainImageCount, pSwapchainImages);
+
+    if(pSwapchainImages && object->fs_hack_enabled){
+        if(*pSwapchainImageCount > object->n_images)
+            *pSwapchainImageCount = object->n_images;
+        for(i = 0; i < *pSwapchainImageCount ; ++i)
+            pSwapchainImages[i] = object->fs_hack_images[i].user_image;
+        return *pSwapchainImageCount == object->n_images ? VK_SUCCESS : VK_INCOMPLETE;
+    }
+
+    return device->funcs.p_vkGetSwapchainImagesKHR(device->device, object->swapchain, pSwapchainImageCount, pSwapchainImages);
+}
+
+static uint32_t get_queue_index(VkQueue queue)
+{
+    uint32_t i;
+    for(i = 0; i < queue->device->max_queue_families; ++i){
+        if(queue->device->queues[i] == queue)
+            return i;
+    }
+    WARN("couldn't find queue\n");
+    return -1;
+}
+
+static VkCommandBuffer create_hack_cmd(VkQueue queue, struct VkSwapchainKHR_T *swapchain, uint32_t queue_idx)
+{
+#if defined(USE_STRUCT_CONVERSION)
+    VkCommandBufferAllocateInfo_host allocInfo = {0};
+#else
+    VkCommandBufferAllocateInfo allocInfo = {0};
+#endif
+    VkCommandBuffer cmd;
+    VkResult result;
+
+    if(!swapchain->cmd_pools[queue_idx]){
+        VkCommandPoolCreateInfo poolInfo = {0};
+
+        poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
+        poolInfo.queueFamilyIndex = queue_idx;
+
+        result = queue->device->funcs.p_vkCreateCommandPool(queue->device->device, &poolInfo, NULL, &swapchain->cmd_pools[queue_idx]);
+        if(result != VK_SUCCESS){
+            ERR("vkCreateCommandPool failed, res=%d\n", result);
+            return NULL;
+        }
+    }
+
+    allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
+    allocInfo.commandPool = swapchain->cmd_pools[queue_idx];
+    allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
+    allocInfo.commandBufferCount = 1;
+
+    result = queue->device->funcs.p_vkAllocateCommandBuffers(queue->device->device, &allocInfo, &cmd);
+    if(result != VK_SUCCESS){
+        ERR("vkAllocateCommandBuffers failed, res=%d\n", result);
+        return NULL;
+    }
+
+    return cmd;
+}
+
+static VkResult init_blit_images(VkDevice device, struct VkSwapchainKHR_T *swapchain)
+{
+    VkResult res;
+    VkSamplerCreateInfo samplerInfo = {0};
+    VkDescriptorPoolSize poolSizes[2] = {{0}, {0}};
+    VkDescriptorPoolCreateInfo poolInfo = {0};
+    VkDescriptorSetLayoutBinding layoutBindings[2] = {{0}, {0}};
+    VkDescriptorSetLayoutCreateInfo descriptorLayoutInfo = {0};
+    VkPipelineLayoutCreateInfo pipelineLayoutInfo = {0};
+    VkPushConstantRange pushConstants;
+    VkShaderModuleCreateInfo shaderInfo = {0};
+    VkShaderModule shaderModule = 0;
+    VkDeviceSize blitMemTotal = 0, offs;
+    VkImageCreateInfo imageInfo = {0};
+#if defined(USE_STRUCT_CONVERSION)
+    VkMemoryRequirements_host blitMemReq;
+    VkMemoryAllocateInfo_host allocInfo = {0};
+    VkPhysicalDeviceMemoryProperties_host memProperties;
+    VkImageViewCreateInfo_host viewInfo = {0};
+#else
+    VkMemoryRequirements blitMemReq;
+    VkMemoryAllocateInfo allocInfo = {0};
+    VkPhysicalDeviceMemoryProperties memProperties;
+    VkImageViewCreateInfo viewInfo = {0};
+#endif
+    uint32_t blit_memory_type = -1, i;
+
+    samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
+    samplerInfo.magFilter = VK_FILTER_LINEAR;
+    samplerInfo.minFilter = VK_FILTER_LINEAR;
+    samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
+    samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
+    samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
+    samplerInfo.anisotropyEnable = VK_FALSE;
+    samplerInfo.maxAnisotropy = 1;
+    samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK;
+    samplerInfo.unnormalizedCoordinates = VK_FALSE;
+    samplerInfo.compareEnable = VK_FALSE;
+    samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS;
+    samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
+    samplerInfo.mipLodBias = 0.0f;
+    samplerInfo.minLod = 0.0f;
+    samplerInfo.maxLod = 0.0f;
+
+    res = device->funcs.p_vkCreateSampler(device->device, &samplerInfo, NULL, &swapchain->sampler);
+    if(res != VK_SUCCESS)
+    {
+        WARN("vkCreateSampler failed, res=%d\n", res);
+        return res;
+    }
+
+    poolSizes[0].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+    poolSizes[0].descriptorCount = swapchain->n_images;
+    poolSizes[1].type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
+    poolSizes[1].descriptorCount = swapchain->n_images;
+
+    poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
+    poolInfo.poolSizeCount = 2;
+    poolInfo.pPoolSizes = poolSizes;
+    poolInfo.maxSets = swapchain->n_images;
+
+    res = device->funcs.p_vkCreateDescriptorPool(device->device, &poolInfo, NULL, &swapchain->descriptor_pool);
+    if(res != VK_SUCCESS){
+        ERR("vkCreateDescriptorPool: %d\n", res);
+        goto fail;
+    }
+
+    layoutBindings[0].binding = 0;
+    layoutBindings[0].descriptorCount = 1;
+    layoutBindings[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+    layoutBindings[0].pImmutableSamplers = NULL;
+    layoutBindings[0].stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
+
+    layoutBindings[1].binding = 1;
+    layoutBindings[1].descriptorCount = 1;
+    layoutBindings[1].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
+    layoutBindings[1].pImmutableSamplers = NULL;
+    layoutBindings[1].stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
+
+    descriptorLayoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
+    descriptorLayoutInfo.bindingCount = 2;
+    descriptorLayoutInfo.pBindings = layoutBindings;
+
+    res = device->funcs.p_vkCreateDescriptorSetLayout(device->device, &descriptorLayoutInfo, NULL, &swapchain->descriptor_set_layout);
+    if(res != VK_SUCCESS){
+        ERR("vkCreateDescriptorSetLayout: %d\n", res);
+        goto fail;
+    }
+
+    pushConstants.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
+    pushConstants.offset = 0;
+    pushConstants.size = 4 * sizeof(float); /* 2 * vec2 */
+
+    pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
+    pipelineLayoutInfo.setLayoutCount = 1;
+    pipelineLayoutInfo.pSetLayouts = &swapchain->descriptor_set_layout;
+    pipelineLayoutInfo.pushConstantRangeCount = 1;
+    pipelineLayoutInfo.pPushConstantRanges = &pushConstants;
+
+    res = device->funcs.p_vkCreatePipelineLayout(device->device, &pipelineLayoutInfo, NULL, &swapchain->pipeline_layout);
+    if(res != VK_SUCCESS){
+        ERR("vkCreatePipelineLayout: %d\n", res);
+        goto fail;
+    }
+
+    if(!(swapchain->surface_usage & VK_IMAGE_USAGE_STORAGE_BIT)){
+        TRACE("using intermediate blit images\n");
+        /* create intermediate blit images */
+        for(i = 0; i < swapchain->n_images; ++i){
+            struct fs_hack_image *hack = &swapchain->fs_hack_images[i];
+
+            imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
+            imageInfo.imageType = VK_IMAGE_TYPE_2D;
+            imageInfo.extent.width = swapchain->real_extent.width;
+            imageInfo.extent.height = swapchain->real_extent.height;
+            imageInfo.extent.depth = 1;
+            imageInfo.mipLevels = 1;
+            imageInfo.arrayLayers = 1;
+            imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
+            imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
+            imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+            imageInfo.usage = VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
+            imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+            imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
+            res = device->funcs.p_vkCreateImage(device->device, &imageInfo, NULL, &hack->blit_image);
+            if(res != VK_SUCCESS){
+                ERR("vkCreateImage failed: %d\n", res);
+                goto fail;
+            }
+
+            device->funcs.p_vkGetImageMemoryRequirements(device->device, hack->blit_image, &blitMemReq);
+
+            offs = blitMemTotal % blitMemReq.alignment;
+            if(offs)
+                blitMemTotal += blitMemReq.alignment - offs;
+
+            blitMemTotal += blitMemReq.size;
+        }
+
+        /* allocate backing memory */
+        device->phys_dev->instance->funcs.p_vkGetPhysicalDeviceMemoryProperties(device->phys_dev->phys_dev, &memProperties);
+
+        for(i = 0; i < memProperties.memoryTypeCount; i++){
+            if((memProperties.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) == VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT){
+                if(blitMemReq.memoryTypeBits & (1 << i)){
+                    blit_memory_type = i;
+                    break;
+                }
+            }
+        }
+
+        if(blit_memory_type == -1){
+            ERR("unable to find suitable memory type\n");
+            res = VK_ERROR_OUT_OF_HOST_MEMORY;
+            goto fail;
+        }
+
+        allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
+        allocInfo.allocationSize = blitMemTotal;
+        allocInfo.memoryTypeIndex = blit_memory_type;
+
+        res = device->funcs.p_vkAllocateMemory(device->device, &allocInfo, NULL, &swapchain->blit_image_memory);
+        if(res != VK_SUCCESS){
+            ERR("vkAllocateMemory: %d\n", res);
+            goto fail;
+        }
+
+        /* bind backing memory and create imageviews */
+        blitMemTotal = 0;
+        for(i = 0; i < swapchain->n_images; ++i){
+            struct fs_hack_image *hack = &swapchain->fs_hack_images[i];
+
+            device->funcs.p_vkGetImageMemoryRequirements(device->device, hack->blit_image, &blitMemReq);
+
+            offs = blitMemTotal % blitMemReq.alignment;
+            if(offs)
+                blitMemTotal += blitMemReq.alignment - offs;
+
+            res = device->funcs.p_vkBindImageMemory(device->device, hack->blit_image, swapchain->blit_image_memory, blitMemTotal);
+            if(res != VK_SUCCESS){
+                ERR("vkBindImageMemory: %d\n", res);
+                goto fail;
+            }
+
+            blitMemTotal += blitMemReq.size;
+        }
+    }else
+        TRACE("blitting directly to swapchain images\n");
+
+    shaderInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
+    shaderInfo.codeSize = sizeof(blit_comp_spv);
+    shaderInfo.pCode = blit_comp_spv;
+
+    res = device->funcs.p_vkCreateShaderModule(device->device, &shaderInfo, NULL, &shaderModule);
+    if(res != VK_SUCCESS){
+        ERR("vkCreateShaderModule: %d\n", res);
+        goto fail;
+    }
+
+    /* create imageviews */
+    for(i = 0; i < swapchain->n_images; ++i){
+        struct fs_hack_image *hack = &swapchain->fs_hack_images[i];
+
+        viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
+        viewInfo.image = hack->blit_image ? hack->blit_image : hack->swapchain_image;
+        viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
+        viewInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
+        viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+        viewInfo.subresourceRange.baseMipLevel = 0;
+        viewInfo.subresourceRange.levelCount = 1;
+        viewInfo.subresourceRange.baseArrayLayer = 0;
+        viewInfo.subresourceRange.layerCount = 1;
+
+        res = device->funcs.p_vkCreateImageView(device->device, &viewInfo, NULL, &hack->blit_view);
+        if(res != VK_SUCCESS){
+            ERR("vkCreateImageView(blit): %d\n", res);
+            goto fail;
+        }
+
+        res = create_descriptor_set(device, swapchain, hack);
+        if(res != VK_SUCCESS)
+            goto fail;
+
+        res = create_pipeline(device, swapchain, hack, shaderModule);
+        if(res != VK_SUCCESS)
+            goto fail;
+    }
+
+    device->funcs.p_vkDestroyShaderModule(device->device, shaderModule, NULL);
+
+    return VK_SUCCESS;
+
+fail:
+    for(i = 0; i < swapchain->n_images; ++i){
+        struct fs_hack_image *hack = &swapchain->fs_hack_images[i];
+
+        device->funcs.p_vkDestroyPipeline(device->device, hack->pipeline, NULL);
+        hack->pipeline = VK_NULL_HANDLE;
+
+        device->funcs.p_vkFreeDescriptorSets(device->device, swapchain->descriptor_pool, 1, &hack->descriptor_set);
+        hack->descriptor_set = VK_NULL_HANDLE;
+
+        device->funcs.p_vkDestroyImageView(device->device, hack->blit_view, NULL);
+        hack->blit_view = VK_NULL_HANDLE;
+
+        device->funcs.p_vkDestroyImage(device->device, hack->blit_image, NULL);
+        hack->blit_image = VK_NULL_HANDLE;
+    }
+
+    device->funcs.p_vkDestroyShaderModule(device->device, shaderModule, NULL);
+
+    device->funcs.p_vkDestroyPipelineLayout(device->device, swapchain->pipeline_layout, NULL);
+    swapchain->pipeline_layout = VK_NULL_HANDLE;
+
+    device->funcs.p_vkDestroyDescriptorSetLayout(device->device, swapchain->descriptor_set_layout, NULL);
+    swapchain->descriptor_set_layout = VK_NULL_HANDLE;
+
+    device->funcs.p_vkDestroyDescriptorPool(device->device, swapchain->descriptor_pool, NULL);
+    swapchain->descriptor_pool = VK_NULL_HANDLE;
+
+    device->funcs.p_vkFreeMemory(device->device, swapchain->blit_image_memory, NULL);
+    swapchain->blit_image_memory = VK_NULL_HANDLE;
+
+    device->funcs.p_vkDestroySampler(device->device, swapchain->sampler, NULL);
+    swapchain->sampler = VK_NULL_HANDLE;
+
+    return res;
+}
+
+static VkResult record_compute_cmd(VkDevice device, struct VkSwapchainKHR_T *swapchain, struct fs_hack_image *hack)
+{
+    VkResult result;
+    VkImageCopy region = {0};
+#if defined(USE_STRUCT_CONVERSION)
+    VkImageMemoryBarrier_host barriers[3] = {{0}};
+    VkCommandBufferBeginInfo_host beginInfo = {0};
+#else
+    VkImageMemoryBarrier barriers[3] = {{0}};
+    VkCommandBufferBeginInfo beginInfo = {0};
+#endif
+    float constants[4];
+
+    TRACE("recording compute command\n");
+
+#if 0
+    /* DOOM runs out of memory when allocating blit images after loading. */
+    if(!swapchain->blit_image_memory){
+        result = init_blit_images(device, swapchain);
+        if(result != VK_SUCCESS)
+            return result;
+    }
+#endif
+
+    beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
+    beginInfo.flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT;
+
+    device->funcs.p_vkBeginCommandBuffer(hack->cmd, &beginInfo);
+
+    /* transition user image from GENERAL to SHADER_READ */
+    barriers[0].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+    barriers[0].oldLayout = VK_IMAGE_LAYOUT_GENERAL;
+    barriers[0].newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+    barriers[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+    barriers[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+    barriers[0].image = hack->user_image;
+    barriers[0].subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+    barriers[0].subresourceRange.baseMipLevel = 0;
+    barriers[0].subresourceRange.levelCount = 1;
+    barriers[0].subresourceRange.baseArrayLayer = 0;
+    barriers[0].subresourceRange.layerCount = 1;
+    barriers[0].srcAccessMask = 0;
+    barriers[0].dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
+
+    /* transition blit image from whatever to GENERAL */
+    barriers[1].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+    barriers[1].oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+    barriers[1].newLayout = VK_IMAGE_LAYOUT_GENERAL;
+    barriers[1].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+    barriers[1].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+    barriers[1].image = hack->blit_image ? hack->blit_image : hack->swapchain_image;
+    barriers[1].subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+    barriers[1].subresourceRange.baseMipLevel = 0;
+    barriers[1].subresourceRange.levelCount = 1;
+    barriers[1].subresourceRange.baseArrayLayer = 0;
+    barriers[1].subresourceRange.layerCount = 1;
+    barriers[1].srcAccessMask = 0;
+    barriers[1].dstAccessMask = VK_ACCESS_SHADER_WRITE_BIT;
+
+    device->funcs.p_vkCmdPipelineBarrier(
+            hack->cmd,
+            VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
+            VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
+            0,
+            0, NULL,
+            0, NULL,
+            2, barriers
+    );
+
+    /* perform blit shader */
+    device->funcs.p_vkCmdBindPipeline(hack->cmd,
+            VK_PIPELINE_BIND_POINT_COMPUTE, hack->pipeline);
+
+    device->funcs.p_vkCmdBindDescriptorSets(hack->cmd,
+            VK_PIPELINE_BIND_POINT_COMPUTE, swapchain->pipeline_layout,
+            0, 1, &hack->descriptor_set, 0, NULL);
+
+    /* vec2: blit dst offset in real coords */
+    constants[0] = swapchain->blit_dst.offset.x;
+    constants[1] = swapchain->blit_dst.offset.y;
+    /* vec2: blit dst extents in real coords */
+    constants[2] = swapchain->blit_dst.extent.width;
+    constants[3] = swapchain->blit_dst.extent.height;
+    device->funcs.p_vkCmdPushConstants(hack->cmd,
+            swapchain->pipeline_layout, VK_SHADER_STAGE_COMPUTE_BIT,
+            0, sizeof(constants), constants);
+
+    /* local sizes in shader are 8 */
+    device->funcs.p_vkCmdDispatch(hack->cmd, ceil(swapchain->real_extent.width / 8.),
+            ceil(swapchain->real_extent.height / 8.), 1);
+
+    /* transition user image from SHADER_READ to GENERAL */
+    barriers[0].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+    barriers[0].oldLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+    barriers[0].newLayout = VK_IMAGE_LAYOUT_GENERAL;
+    barriers[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+    barriers[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+    barriers[0].image = hack->user_image;
+    barriers[0].subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+    barriers[0].subresourceRange.baseMipLevel = 0;
+    barriers[0].subresourceRange.levelCount = 1;
+    barriers[0].subresourceRange.baseArrayLayer = 0;
+    barriers[0].subresourceRange.layerCount = 1;
+    barriers[0].srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
+    barriers[0].dstAccessMask = 0;
+
+    device->funcs.p_vkCmdPipelineBarrier(
+            hack->cmd,
+            VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
+            VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
+            0,
+            0, NULL,
+            0, NULL,
+            1, barriers
+    );
+
+    if(hack->blit_image){
+        /* transition blit image layout from GENERAL to TRANSFER_SRC
+         * and access from SHADER_WRITE_BIT to TRANSFER_READ_BIT  */
+        barriers[0].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+        barriers[0].oldLayout = VK_IMAGE_LAYOUT_GENERAL;
+        barriers[0].newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
+        barriers[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+        barriers[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+        barriers[0].image = hack->blit_image;
+        barriers[0].subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+        barriers[0].subresourceRange.baseMipLevel = 0;
+        barriers[0].subresourceRange.levelCount = 1;
+        barriers[0].subresourceRange.baseArrayLayer = 0;
+        barriers[0].subresourceRange.layerCount = 1;
+        barriers[0].srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT;
+        barriers[0].dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
+
+        /* transition swapchain image from whatever to PRESENT_SRC */
+        barriers[1].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+        barriers[1].oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+        barriers[1].newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
+        barriers[1].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+        barriers[1].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+        barriers[1].image = hack->swapchain_image;
+        barriers[1].subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+        barriers[1].subresourceRange.baseMipLevel = 0;
+        barriers[1].subresourceRange.levelCount = 1;
+        barriers[1].subresourceRange.baseArrayLayer = 0;
+        barriers[1].subresourceRange.layerCount = 1;
+        barriers[1].srcAccessMask = 0;
+        barriers[1].dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+
+        device->funcs.p_vkCmdPipelineBarrier(
+                hack->cmd,
+                VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
+                VK_PIPELINE_STAGE_TRANSFER_BIT,
+                0,
+                0, NULL,
+                0, NULL,
+                2, barriers
+        );
+
+        /* copy from blit image to swapchain image */
+        region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+        region.srcSubresource.layerCount = 1;
+        region.srcOffset.x = 0;
+        region.srcOffset.y = 0;
+        region.srcOffset.z = 0;
+        region.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+        region.dstSubresource.layerCount = 1;
+        region.dstOffset.x = 0;
+        region.dstOffset.y = 0;
+        region.dstOffset.z = 0;
+        region.extent.width = swapchain->real_extent.width;
+        region.extent.height = swapchain->real_extent.height;
+        region.extent.depth = 1;
+
+        device->funcs.p_vkCmdCopyImage(hack->cmd,
+                hack->blit_image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+                hack->swapchain_image, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
+                1, &region);
+    }else{
+        /* transition swapchain image from GENERAL to PRESENT_SRC */
+        barriers[0].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+        barriers[0].oldLayout = VK_IMAGE_LAYOUT_GENERAL;
+        barriers[0].newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
+        barriers[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+        barriers[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+        barriers[0].image = hack->swapchain_image;
+        barriers[0].subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+        barriers[0].subresourceRange.baseMipLevel = 0;
+        barriers[0].subresourceRange.levelCount = 1;
+        barriers[0].subresourceRange.baseArrayLayer = 0;
+        barriers[0].subresourceRange.layerCount = 1;
+        barriers[0].srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT;
+        barriers[0].dstAccessMask = 0;
+
+        device->funcs.p_vkCmdPipelineBarrier(
+                hack->cmd,
+                VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
+                VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
+                0,
+                0, NULL,
+                0, NULL,
+                1, barriers
+        );
+    }
+
+    result = device->funcs.p_vkEndCommandBuffer(hack->cmd);
+    if(result != VK_SUCCESS){
+        ERR("vkEndCommandBuffer: %d\n", result);
+        return result;
+    }
+
+    return VK_SUCCESS;
+}
+
+static VkResult record_graphics_cmd(VkDevice device, struct VkSwapchainKHR_T *swapchain, struct fs_hack_image *hack)
+{
+    VkResult result;
+    VkImageBlit blitregion = {0};
+    VkImageSubresourceRange range = {0};
+    VkClearColorValue black = {{0.f, 0.f, 0.f}};
+#if defined(USE_STRUCT_CONVERSION)
+    VkImageMemoryBarrier_host barriers[2] = {{0}};
+    VkCommandBufferBeginInfo_host beginInfo = {0};
+#else
+    VkImageMemoryBarrier barriers[2] = {{0}};
+    VkCommandBufferBeginInfo beginInfo = {0};
+#endif
+
+    TRACE("recording graphics command\n");
+
+    beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
+    beginInfo.flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT;
+
+    device->funcs.p_vkBeginCommandBuffer(hack->cmd, &beginInfo);
+
+    /* transition user image from GENERAL to TRANSFER_SRC_OPTIMAL */
+    barriers[0].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+    barriers[0].oldLayout = VK_IMAGE_LAYOUT_GENERAL;
+    barriers[0].newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
+    barriers[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+    barriers[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+    barriers[0].image = hack->user_image;
+    barriers[0].subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+    barriers[0].subresourceRange.baseMipLevel = 0;
+    barriers[0].subresourceRange.levelCount = 1;
+    barriers[0].subresourceRange.baseArrayLayer = 0;
+    barriers[0].subresourceRange.layerCount = 1;
+    barriers[0].srcAccessMask = 0;
+    barriers[0].dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
+
+    /* transition real image from whatever to TRANSFER_DST_OPTIMAL */
+    barriers[1].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+    barriers[1].oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+    barriers[1].newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
+    barriers[1].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+    barriers[1].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+    barriers[1].image = hack->swapchain_image;
+    barriers[1].subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+    barriers[1].subresourceRange.baseMipLevel = 0;
+    barriers[1].subresourceRange.levelCount = 1;
+    barriers[1].subresourceRange.baseArrayLayer = 0;
+    barriers[1].subresourceRange.layerCount = 1;
+    barriers[1].srcAccessMask = 0;
+    barriers[1].dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+
+    device->funcs.p_vkCmdPipelineBarrier(
+            hack->cmd,
+            VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
+            VK_PIPELINE_STAGE_TRANSFER_BIT,
+            0,
+            0, NULL,
+            0, NULL,
+            2, barriers
+    );
+
+    /* clear the image */
+    range.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+    range.baseMipLevel = 0;
+    range.levelCount = 1;
+    range.baseArrayLayer = 0;
+    range.layerCount = 1;
+
+    device->funcs.p_vkCmdClearColorImage(
+            hack->cmd, hack->swapchain_image,
+            VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+            &black, 1, &range);
+
+    /* perform blit */
+    blitregion.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+    blitregion.srcSubresource.layerCount = 1;
+    blitregion.srcOffsets[0].x = 0;
+    blitregion.srcOffsets[0].y = 0;
+    blitregion.srcOffsets[0].z = 0;
+    blitregion.srcOffsets[1].x = swapchain->user_extent.width;
+    blitregion.srcOffsets[1].y = swapchain->user_extent.height;
+    blitregion.srcOffsets[1].z = 1;
+    blitregion.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+    blitregion.dstSubresource.layerCount = 1;
+    blitregion.dstOffsets[0].x = swapchain->blit_dst.offset.x;
+    blitregion.dstOffsets[0].y = swapchain->blit_dst.offset.y;
+    blitregion.dstOffsets[0].z = 0;
+    blitregion.dstOffsets[1].x = swapchain->blit_dst.offset.x + swapchain->blit_dst.extent.width;
+    blitregion.dstOffsets[1].y = swapchain->blit_dst.offset.y + swapchain->blit_dst.extent.height;
+    blitregion.dstOffsets[1].z = 1;
+
+    device->funcs.p_vkCmdBlitImage(hack->cmd,
+            hack->user_image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+            hack->swapchain_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+            1, &blitregion, VK_FILTER_LINEAR /* CUBIC_IMG? */);
+
+    /* transition user image from TRANSFER_SRC_OPTIMAL to GENERAL */
+    barriers[0].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+    barriers[0].oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
+    barriers[0].newLayout = VK_IMAGE_LAYOUT_GENERAL;
+    barriers[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+    barriers[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+    barriers[0].image = hack->user_image;
+    barriers[0].subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+    barriers[0].subresourceRange.baseMipLevel = 0;
+    barriers[0].subresourceRange.levelCount = 1;
+    barriers[0].subresourceRange.baseArrayLayer = 0;
+    barriers[0].subresourceRange.layerCount = 1;
+    barriers[0].srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
+    barriers[0].dstAccessMask = 0;
+
+    /* transition real image from TRANSFER_DST to PRESENT_SRC */
+    barriers[1].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+    barriers[1].oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
+    barriers[1].newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
+    barriers[1].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+    barriers[1].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+    barriers[1].image = hack->swapchain_image;
+    barriers[1].subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+    barriers[1].subresourceRange.baseMipLevel = 0;
+    barriers[1].subresourceRange.levelCount = 1;
+    barriers[1].subresourceRange.baseArrayLayer = 0;
+    barriers[1].subresourceRange.layerCount = 1;
+    barriers[1].srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+    barriers[1].dstAccessMask = 0;
+
+    device->funcs.p_vkCmdPipelineBarrier(
+            hack->cmd,
+            VK_PIPELINE_STAGE_TRANSFER_BIT,
+            VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
+            0,
+            0, NULL,
+            0, NULL,
+            2, barriers
+    );
+
+    result = device->funcs.p_vkEndCommandBuffer(hack->cmd);
+    if(result != VK_SUCCESS){
+        ERR("vkEndCommandBuffer: %d\n", result);
+        return result;
+    }
+
+    return VK_SUCCESS;
+}
+
+VkResult WINAPI wine_vkQueuePresentKHR(VkQueue queue, const VkPresentInfoKHR *pPresentInfo)
+{
+    VkResult res;
+    VkPresentInfoKHR our_presentInfo;
+    VkSwapchainKHR *arr;
+    VkCommandBuffer *blit_cmds = NULL;
+    VkSubmitInfo submitInfo = {0};
+    VkSemaphore blit_sema;
+    struct VkSwapchainKHR_T *swapchain;
+    uint32_t i, n_hacks = 0;
+    uint32_t queue_idx;
+
+    TRACE("%p, %p\n", queue, pPresentInfo);
+
+    our_presentInfo = *pPresentInfo;
+
+    for(i = 0; i < our_presentInfo.swapchainCount; ++i){
+        swapchain = (struct VkSwapchainKHR_T *)(UINT_PTR)our_presentInfo.pSwapchains[i];
+
+        if(swapchain->fs_hack_enabled){
+            struct fs_hack_image *hack = &swapchain->fs_hack_images[our_presentInfo.pImageIndices[i]];
+
+            if(!blit_cmds){
+                queue_idx = get_queue_index(queue);
+                blit_cmds = heap_alloc(our_presentInfo.swapchainCount * sizeof(VkCommandBuffer));
+                blit_sema = hack->blit_finished;
+            }
+
+            if(!hack->cmd || hack->cmd_queue_idx != queue_idx){
+                if(hack->cmd)
+                    queue->device->funcs.p_vkFreeCommandBuffers(queue->device->device,
+                            swapchain->cmd_pools[hack->cmd_queue_idx],
+                            1, &hack->cmd);
+
+                hack->cmd_queue_idx = queue_idx;
+                hack->cmd = create_hack_cmd(queue, swapchain, queue_idx);
+
+                if(!hack->cmd){
+                    heap_free(blit_cmds);
+                    return VK_ERROR_DEVICE_LOST;
+                }
+
+                if(queue->device->queue_props[queue_idx].queueFlags & VK_QUEUE_GRAPHICS_BIT)
+                    res = record_graphics_cmd(queue->device, swapchain, hack);
+                else if(queue->device->queue_props[queue_idx].queueFlags & VK_QUEUE_COMPUTE_BIT)
+                    res = record_compute_cmd(queue->device, swapchain, hack);
+                else{
+                    ERR("Present queue is neither graphics nor compute queue!\n");
+                    res = VK_ERROR_DEVICE_LOST;
+                }
+
+                if(res != VK_SUCCESS){
+                    queue->device->funcs.p_vkFreeCommandBuffers(queue->device->device,
+                            swapchain->cmd_pools[hack->cmd_queue_idx],
+                            1, &hack->cmd);
+                    hack->cmd = NULL;
+                    heap_free(blit_cmds);
+                    return res;
+                }
+            }
+
+            blit_cmds[n_hacks] = hack->cmd;
+
+            ++n_hacks;
+        }
+    }
+
+    if(n_hacks > 0){
+        VkPipelineStageFlags waitStage, *waitStages, *waitStages_arr = NULL;
+
+        if(pPresentInfo->waitSemaphoreCount > 1){
+            waitStages_arr = heap_alloc(sizeof(VkPipelineStageFlags) * pPresentInfo->waitSemaphoreCount);
+            for(i = 0; i < pPresentInfo->waitSemaphoreCount; ++i)
+                waitStages_arr[i] = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT;
+            waitStages = waitStages_arr;
+        }else{
+            waitStage = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT;
+            waitStages = &waitStage;
+        }
+
+        /* blit user image to real image */
+        submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
+        submitInfo.waitSemaphoreCount = pPresentInfo->waitSemaphoreCount;
+        submitInfo.pWaitSemaphores = pPresentInfo->pWaitSemaphores;
+        submitInfo.pWaitDstStageMask = waitStages;
+        submitInfo.commandBufferCount = n_hacks;
+        submitInfo.pCommandBuffers = blit_cmds;
+        submitInfo.signalSemaphoreCount = 1;
+        submitInfo.pSignalSemaphores = &blit_sema;
+
+        res = queue->device->funcs.p_vkQueueSubmit(queue->queue, 1, &submitInfo, VK_NULL_HANDLE);
+        if(res != VK_SUCCESS)
+            ERR("vkQueueSubmit: %d\n", res);
+
+        heap_free(waitStages_arr);
+        heap_free(blit_cmds);
+
+        our_presentInfo.waitSemaphoreCount = 1;
+        our_presentInfo.pWaitSemaphores = &blit_sema;
+    }
+
+    arr = heap_alloc(our_presentInfo.swapchainCount * sizeof(VkSwapchainKHR));
+    if(!arr){
+        ERR("Failed to allocate memory for swapchain array\n");
+        return VK_ERROR_OUT_OF_HOST_MEMORY;
+    }
+
+    for(i = 0; i < our_presentInfo.swapchainCount; ++i)
+        arr[i] = ((struct VkSwapchainKHR_T *)(UINT_PTR)our_presentInfo.pSwapchains[i])->swapchain;
+
+    our_presentInfo.pSwapchains = arr;
+
+    res = queue->device->funcs.p_vkQueuePresentKHR(queue->queue, &our_presentInfo);
+
+    heap_free(arr);
+
+    return res;
+
+}
+
+void WINAPI wine_vkCmdPipelineBarrier(VkCommandBuffer commandBuffer,
+        VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask,
+        VkDependencyFlags dependencyFlags, uint32_t memoryBarrierCount,
+        const VkMemoryBarrier *pMemoryBarriers, uint32_t bufferMemoryBarrierCount,
+        const VkBufferMemoryBarrier *pBufferMemoryBarriers, uint32_t imageMemoryBarrierCount,
+        const VkImageMemoryBarrier *pImageMemoryBarriers)
+{
+#if defined(USE_STRUCT_CONVERSION)
+    VkBufferMemoryBarrier_host *pBufferMemoryBarriers_host;
+#endif
+    VkImageMemoryBarrier_host *pImageMemoryBarriers_host = NULL;
+    uint32_t i, j, k;
+    int old, new;
+
+    TRACE("%p, %#x, %#x, %#x, %u, %p, %u, %p, %u, %p\n", commandBuffer, srcStageMask, dstStageMask, dependencyFlags, memoryBarrierCount, pMemoryBarriers, bufferMemoryBarrierCount, pBufferMemoryBarriers, imageMemoryBarrierCount, pImageMemoryBarriers);
+
+#if defined(USE_STRUCT_CONVERSION)
+    pBufferMemoryBarriers_host = convert_VkBufferMemoryBarrier_array_win_to_host(pBufferMemoryBarriers, bufferMemoryBarrierCount);
+    pImageMemoryBarriers_host = convert_VkImageMemoryBarrier_array_win_to_host(pImageMemoryBarriers, imageMemoryBarrierCount);
+#endif
+
+    /* if the client is trying to transition a user image to PRESENT_SRC,
+     * transition it to GENERAL instead. */
+    EnterCriticalSection(&commandBuffer->device->swapchain_lock);
+    for(i = 0; i < imageMemoryBarrierCount; ++i){
+        old = pImageMemoryBarriers[i].oldLayout == VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
+        new = pImageMemoryBarriers[i].newLayout == VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
+        if(old || new){
+            for(j = 0; j < commandBuffer->device->num_swapchains; ++j){
+                struct VkSwapchainKHR_T *swapchain = commandBuffer->device->swapchains[j];
+                if(swapchain->fs_hack_enabled){
+                    for(k = 0; k < swapchain->n_images; ++k){
+                        struct fs_hack_image *hack = &swapchain->fs_hack_images[k];
+                        if(pImageMemoryBarriers[i].image == hack->user_image){
+#if !defined(USE_STRUCT_CONVERSION)
+                            if(!pImageMemoryBarriers_host)
+                                pImageMemoryBarriers_host = convert_VkImageMemoryBarrier_array_win_to_host(pImageMemoryBarriers, imageMemoryBarrierCount);
+#endif
+                            if(old)
+                                pImageMemoryBarriers_host[i].oldLayout = VK_IMAGE_LAYOUT_GENERAL;
+                            if(new)
+                                pImageMemoryBarriers_host[i].newLayout = VK_IMAGE_LAYOUT_GENERAL;
+                            goto next;
+                        }
+                    }
+                }
+            }
+        }
+next:   ;
+    }
+    LeaveCriticalSection(&commandBuffer->device->swapchain_lock);
+
+    commandBuffer->device->funcs.p_vkCmdPipelineBarrier(commandBuffer->command_buffer,
+            srcStageMask, dstStageMask, dependencyFlags, memoryBarrierCount,
+            pMemoryBarriers, bufferMemoryBarrierCount,
+#if defined(USE_STRUCT_CONVERSION)
+            pBufferMemoryBarriers_host, imageMemoryBarrierCount, pImageMemoryBarriers_host
+#else
+            pBufferMemoryBarriers, imageMemoryBarrierCount,
+            pImageMemoryBarriers_host ? (VkImageMemoryBarrier*)pImageMemoryBarriers_host : pImageMemoryBarriers
+#endif
+            );
+
+#if defined(USE_STRUCT_CONVERSION)
+    free_VkBufferMemoryBarrier_array(pBufferMemoryBarriers_host, bufferMemoryBarrierCount);
+#else
+    if(pImageMemoryBarriers_host)
+#endif
+        free_VkImageMemoryBarrier_array(pImageMemoryBarriers_host, imageMemoryBarrierCount);
+}
+
+VkDevice WINAPI __wine_get_native_VkDevice(VkDevice device)
+{
+    return device->device;
+}
+
+VkInstance WINAPI __wine_get_native_VkInstance(VkInstance instance)
+{
+    return instance->instance;
+}
+
+VkPhysicalDevice WINAPI __wine_get_native_VkPhysicalDevice(VkPhysicalDevice phys_dev)
+{
+    return phys_dev->phys_dev;
+}
+
+VkQueue WINAPI __wine_get_native_VkQueue(VkQueue queue)
+{
+    return queue->queue;
+}
+
+VkPhysicalDevice WINAPI __wine_get_wrapped_VkPhysicalDevice(VkInstance instance, VkPhysicalDevice native_phys_dev)
+{
+    uint32_t i;
+    for(i = 0; i < instance->phys_dev_count; ++i){
+        if(instance->phys_devs[i]->phys_dev == native_phys_dev)
+            return instance->phys_devs[i];
+    }
+    WARN("Unknown native physical device: %p\n", native_phys_dev);
+    return NULL;
+}
+
diff --git a/dlls/winevulkan/vulkan_private.h b/dlls/winevulkan/vulkan_private.h
index 17072d2341..41fe5dadd1 100644
--- a/dlls/winevulkan/vulkan_private.h
+++ b/dlls/winevulkan/vulkan_private.h
@@ -72,11 +72,18 @@ struct VkDevice_T
     struct wine_vk_base base;
     struct vulkan_device_funcs funcs;
     VkDevice device; /* native device */
+    struct VkPhysicalDevice_T *phys_dev; /* parent */
 
     struct VkQueue_T **queues;
     uint32_t max_queue_families;
 
     unsigned int quirks;
+
+    uint32_t num_swapchains;
+    struct VkSwapchainKHR_T **swapchains;
+    VkQueueFamilyProperties *queue_props;
+
+    CRITICAL_SECTION swapchain_lock;
 };
 
 struct VkInstance_T
@@ -130,6 +137,40 @@ static inline VkCommandPool wine_cmd_pool_to_handle(struct wine_cmd_pool *cmd_po
     return (VkCommandPool)(uintptr_t)cmd_pool;
 }
 
+struct fs_hack_image
+{
+    uint32_t cmd_queue_idx;
+    VkCommandBuffer cmd;
+    VkImage swapchain_image;
+    VkImage blit_image;
+    VkImage user_image;
+    VkSemaphore blit_finished;
+    VkImageView user_view, blit_view;
+    VkDescriptorSet descriptor_set;
+    VkPipeline pipeline;
+};
+
+struct VkSwapchainKHR_T
+{
+    struct wine_vk_base base;
+    VkSwapchainKHR swapchain; /* native swapchain */
+
+    /* fs hack data below */
+    BOOL fs_hack_enabled;
+    VkExtent2D user_extent;
+    VkExtent2D real_extent;
+    VkImageUsageFlags surface_usage;
+    VkRect2D blit_dst;
+    VkCommandPool *cmd_pools; /* VkCommandPool[device->max_queue_families] */
+    VkDeviceMemory user_image_memory, blit_image_memory;
+    uint32_t n_images;
+    struct fs_hack_image *fs_hack_images; /* struct fs_hack_image[n_images] */
+    VkSampler sampler;
+    VkDescriptorPool descriptor_pool;
+    VkDescriptorSetLayout descriptor_set_layout;
+    VkPipelineLayout pipeline_layout;
+};
+
 void *wine_vk_get_device_proc_addr(const char *name) DECLSPEC_HIDDEN;
 void *wine_vk_get_instance_proc_addr(const char *name) DECLSPEC_HIDDEN;
 
diff --git a/dlls/winevulkan/vulkan_thunks.c b/dlls/winevulkan/vulkan_thunks.c
index fecf9ab502..e53154d1ef 100644
--- a/dlls/winevulkan/vulkan_thunks.c
+++ b/dlls/winevulkan/vulkan_thunks.c
@@ -376,69 +376,6 @@ static inline void free_VkBufferImageCopy_array(VkBufferImageCopy_host *in, uint
     free_VkIndirectCommandsStreamNV_array((VkIndirectCommandsStreamNV_host *)in->pStreams, in->streamCount);
 }
 
-static inline VkBufferMemoryBarrier_host *convert_VkBufferMemoryBarrier_array_win_to_host(const VkBufferMemoryBarrier *in, uint32_t count)
-{
-    VkBufferMemoryBarrier_host *out;
-    unsigned int i;
-
-    if (!in) return NULL;
-
-    out = heap_alloc(count * sizeof(*out));
-    for (i = 0; i < count; i++)
-    {
-        out[i].sType = in[i].sType;
-        out[i].pNext = in[i].pNext;
-        out[i].srcAccessMask = in[i].srcAccessMask;
-        out[i].dstAccessMask = in[i].dstAccessMask;
-        out[i].srcQueueFamilyIndex = in[i].srcQueueFamilyIndex;
-        out[i].dstQueueFamilyIndex = in[i].dstQueueFamilyIndex;
-        out[i].buffer = in[i].buffer;
-        out[i].offset = in[i].offset;
-        out[i].size = in[i].size;
-    }
-
-    return out;
-}
-
-static inline void free_VkBufferMemoryBarrier_array(VkBufferMemoryBarrier_host *in, uint32_t count)
-{
-    if (!in) return;
-
-    heap_free(in);
-}
-
-static inline VkImageMemoryBarrier_host *convert_VkImageMemoryBarrier_array_win_to_host(const VkImageMemoryBarrier *in, uint32_t count)
-{
-    VkImageMemoryBarrier_host *out;
-    unsigned int i;
-
-    if (!in) return NULL;
-
-    out = heap_alloc(count * sizeof(*out));
-    for (i = 0; i < count; i++)
-    {
-        out[i].sType = in[i].sType;
-        out[i].pNext = in[i].pNext;
-        out[i].srcAccessMask = in[i].srcAccessMask;
-        out[i].dstAccessMask = in[i].dstAccessMask;
-        out[i].oldLayout = in[i].oldLayout;
-        out[i].newLayout = in[i].newLayout;
-        out[i].srcQueueFamilyIndex = in[i].srcQueueFamilyIndex;
-        out[i].dstQueueFamilyIndex = in[i].dstQueueFamilyIndex;
-        out[i].image = in[i].image;
-        out[i].subresourceRange = in[i].subresourceRange;
-    }
-
-    return out;
-}
-
-static inline void free_VkImageMemoryBarrier_array(VkImageMemoryBarrier_host *in, uint32_t count)
-{
-    if (!in) return;
-
-    heap_free(in);
-}
-
 static inline VkDescriptorImageInfo_host *convert_VkDescriptorImageInfo_array_win_to_host(const VkDescriptorImageInfo *in, uint32_t count)
 {
     VkDescriptorImageInfo_host *out;
@@ -787,30 +724,6 @@ static inline void free_VkRayTracingPipelineCreateInfoNV_array(VkRayTracingPipel
     heap_free(in);
 }
 
-static inline void convert_VkSwapchainCreateInfoKHR_win_to_host(const VkSwapchainCreateInfoKHR *in, VkSwapchainCreateInfoKHR_host *out)
-{
-    if (!in) return;
-
-    out->sType = in->sType;
-    out->pNext = in->pNext;
-    out->flags = in->flags;
-    out->surface = in->surface;
-    out->minImageCount = in->minImageCount;
-    out->imageFormat = in->imageFormat;
-    out->imageColorSpace = in->imageColorSpace;
-    out->imageExtent = in->imageExtent;
-    out->imageArrayLayers = in->imageArrayLayers;
-    out->imageUsage = in->imageUsage;
-    out->imageSharingMode = in->imageSharingMode;
-    out->queueFamilyIndexCount = in->queueFamilyIndexCount;
-    out->pQueueFamilyIndices = in->pQueueFamilyIndices;
-    out->preTransform = in->preTransform;
-    out->compositeAlpha = in->compositeAlpha;
-    out->presentMode = in->presentMode;
-    out->clipped = in->clipped;
-    out->oldSwapchain = in->oldSwapchain;
-}
-
 static inline VkMappedMemoryRange_host *convert_VkMappedMemoryRange_array_win_to_host(const VkMappedMemoryRange *in, uint32_t count)
 {
     VkMappedMemoryRange_host *out;
@@ -2457,6 +2370,69 @@ void free_VkInstanceCreateInfo_struct_chain(VkInstanceCreateInfo *s)
     s->pNext = NULL;
 }
 
+VkBufferMemoryBarrier_host *convert_VkBufferMemoryBarrier_array_win_to_host(const VkBufferMemoryBarrier *in, uint32_t count)
+{
+    VkBufferMemoryBarrier_host *out;
+    unsigned int i;
+
+    if (!in) return NULL;
+
+    out = heap_alloc(count * sizeof(*out));
+    for (i = 0; i < count; i++)
+    {
+        out[i].sType = in[i].sType;
+        out[i].pNext = in[i].pNext;
+        out[i].srcAccessMask = in[i].srcAccessMask;
+        out[i].dstAccessMask = in[i].dstAccessMask;
+        out[i].srcQueueFamilyIndex = in[i].srcQueueFamilyIndex;
+        out[i].dstQueueFamilyIndex = in[i].dstQueueFamilyIndex;
+        out[i].buffer = in[i].buffer;
+        out[i].offset = in[i].offset;
+        out[i].size = in[i].size;
+    }
+
+    return out;
+}
+
+void free_VkBufferMemoryBarrier_array(VkBufferMemoryBarrier_host *in, uint32_t count)
+{
+    if (!in) return;
+
+    heap_free(in);
+}
+
+VkImageMemoryBarrier_host *convert_VkImageMemoryBarrier_array_win_to_host(const VkImageMemoryBarrier *in, uint32_t count)
+{
+    VkImageMemoryBarrier_host *out;
+    unsigned int i;
+
+    if (!in) return NULL;
+
+    out = heap_alloc(count * sizeof(*out));
+    for (i = 0; i < count; i++)
+    {
+        out[i].sType = in[i].sType;
+        out[i].pNext = in[i].pNext;
+        out[i].srcAccessMask = in[i].srcAccessMask;
+        out[i].dstAccessMask = in[i].dstAccessMask;
+        out[i].oldLayout = in[i].oldLayout;
+        out[i].newLayout = in[i].newLayout;
+        out[i].srcQueueFamilyIndex = in[i].srcQueueFamilyIndex;
+        out[i].dstQueueFamilyIndex = in[i].dstQueueFamilyIndex;
+        out[i].image = in[i].image;
+        out[i].subresourceRange = in[i].subresourceRange;
+    }
+
+    return out;
+}
+
+void free_VkImageMemoryBarrier_array(VkImageMemoryBarrier_host *in, uint32_t count)
+{
+    if (!in) return;
+
+    heap_free(in);
+}
+
 VkResult WINAPI wine_vkAcquireNextImage2KHR(VkDevice device, const VkAcquireNextImageInfoKHR *pAcquireInfo, uint32_t *pImageIndex)
 {
 #if defined(USE_STRUCT_CONVERSION)
@@ -2474,12 +2450,6 @@ VkResult WINAPI wine_vkAcquireNextImage2KHR(VkDevice device, const VkAcquireNext
 #endif
 }
 
-VkResult WINAPI wine_vkAcquireNextImageKHR(VkDevice device, VkSwapchainKHR swapchain, uint64_t timeout, VkSemaphore semaphore, VkFence fence, uint32_t *pImageIndex)
-{
-    TRACE("%p, 0x%s, 0x%s, 0x%s, 0x%s, %p\n", device, wine_dbgstr_longlong(swapchain), wine_dbgstr_longlong(timeout), wine_dbgstr_longlong(semaphore), wine_dbgstr_longlong(fence), pImageIndex);
-    return device->funcs.p_vkAcquireNextImageKHR(device->device, swapchain, timeout, semaphore, fence, pImageIndex);
-}
-
 static VkResult WINAPI wine_vkAcquirePerformanceConfigurationINTEL(VkDevice device, const VkPerformanceConfigurationAcquireInfoINTEL *pAcquireInfo, VkPerformanceConfigurationINTEL *pConfiguration)
 {
     TRACE("%p, %p, %p\n", device, pAcquireInfo, pConfiguration);
@@ -3012,25 +2982,6 @@ static void WINAPI wine_vkCmdNextSubpass2KHR(VkCommandBuffer commandBuffer, cons
     commandBuffer->device->funcs.p_vkCmdNextSubpass2KHR(commandBuffer->command_buffer, pSubpassBeginInfo, pSubpassEndInfo);
 }
 
-void WINAPI wine_vkCmdPipelineBarrier(VkCommandBuffer commandBuffer, VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask, VkDependencyFlags dependencyFlags, uint32_t memoryBarrierCount, const VkMemoryBarrier *pMemoryBarriers, uint32_t bufferMemoryBarrierCount, const VkBufferMemoryBarrier *pBufferMemoryBarriers, uint32_t imageMemoryBarrierCount, const VkImageMemoryBarrier *pImageMemoryBarriers)
-{
-#if defined(USE_STRUCT_CONVERSION)
-    VkBufferMemoryBarrier_host *pBufferMemoryBarriers_host;
-    VkImageMemoryBarrier_host *pImageMemoryBarriers_host;
-    TRACE("%p, %#x, %#x, %#x, %u, %p, %u, %p, %u, %p\n", commandBuffer, srcStageMask, dstStageMask, dependencyFlags, memoryBarrierCount, pMemoryBarriers, bufferMemoryBarrierCount, pBufferMemoryBarriers, imageMemoryBarrierCount, pImageMemoryBarriers);
-
-    pBufferMemoryBarriers_host = convert_VkBufferMemoryBarrier_array_win_to_host(pBufferMemoryBarriers, bufferMemoryBarrierCount);
-    pImageMemoryBarriers_host = convert_VkImageMemoryBarrier_array_win_to_host(pImageMemoryBarriers, imageMemoryBarrierCount);
-    commandBuffer->device->funcs.p_vkCmdPipelineBarrier(commandBuffer->command_buffer, srcStageMask, dstStageMask, dependencyFlags, memoryBarrierCount, pMemoryBarriers, bufferMemoryBarrierCount, pBufferMemoryBarriers_host, imageMemoryBarrierCount, pImageMemoryBarriers_host);
-
-    free_VkBufferMemoryBarrier_array(pBufferMemoryBarriers_host, bufferMemoryBarrierCount);
-    free_VkImageMemoryBarrier_array(pImageMemoryBarriers_host, imageMemoryBarrierCount);
-#else
-    TRACE("%p, %#x, %#x, %#x, %u, %p, %u, %p, %u, %p\n", commandBuffer, srcStageMask, dstStageMask, dependencyFlags, memoryBarrierCount, pMemoryBarriers, bufferMemoryBarrierCount, pBufferMemoryBarriers, imageMemoryBarrierCount, pImageMemoryBarriers);
-    commandBuffer->device->funcs.p_vkCmdPipelineBarrier(commandBuffer->command_buffer, srcStageMask, dstStageMask, dependencyFlags, memoryBarrierCount, pMemoryBarriers, bufferMemoryBarrierCount, pBufferMemoryBarriers, imageMemoryBarrierCount, pImageMemoryBarriers);
-#endif
-}
-
 static void WINAPI wine_vkCmdPreprocessGeneratedCommandsNV(VkCommandBuffer commandBuffer, const VkGeneratedCommandsInfoNV *pGeneratedCommandsInfo)
 {
 #if defined(USE_STRUCT_CONVERSION)
@@ -3561,23 +3512,6 @@ VkResult WINAPI wine_vkCreateShaderModule(VkDevice device, const VkShaderModuleC
     return device->funcs.p_vkCreateShaderModule(device->device, pCreateInfo, NULL, pShaderModule);
 }
 
-VkResult WINAPI wine_vkCreateSwapchainKHR(VkDevice device, const VkSwapchainCreateInfoKHR *pCreateInfo, const VkAllocationCallbacks *pAllocator, VkSwapchainKHR *pSwapchain)
-{
-#if defined(USE_STRUCT_CONVERSION)
-    VkResult result;
-    VkSwapchainCreateInfoKHR_host pCreateInfo_host;
-    TRACE("%p, %p, %p, %p\n", device, pCreateInfo, pAllocator, pSwapchain);
-
-    convert_VkSwapchainCreateInfoKHR_win_to_host(pCreateInfo, &pCreateInfo_host);
-    result = device->funcs.p_vkCreateSwapchainKHR(device->device, &pCreateInfo_host, NULL, pSwapchain);
-
-    return result;
-#else
-    TRACE("%p, %p, %p, %p\n", device, pCreateInfo, pAllocator, pSwapchain);
-    return device->funcs.p_vkCreateSwapchainKHR(device->device, pCreateInfo, NULL, pSwapchain);
-#endif
-}
-
 static VkResult WINAPI wine_vkCreateValidationCacheEXT(VkDevice device, const VkValidationCacheCreateInfoEXT *pCreateInfo, const VkAllocationCallbacks *pAllocator, VkValidationCacheEXT *pValidationCache)
 {
     TRACE("%p, %p, %p, %p\n", device, pCreateInfo, pAllocator, pValidationCache);
@@ -3728,12 +3662,6 @@ void WINAPI wine_vkDestroySurfaceKHR(VkInstance instance, VkSurfaceKHR surface,
     instance->funcs.p_vkDestroySurfaceKHR(instance->instance, surface, NULL);
 }
 
-void WINAPI wine_vkDestroySwapchainKHR(VkDevice device, VkSwapchainKHR swapchain, const VkAllocationCallbacks *pAllocator)
-{
-    TRACE("%p, 0x%s, %p\n", device, wine_dbgstr_longlong(swapchain), pAllocator);
-    device->funcs.p_vkDestroySwapchainKHR(device->device, swapchain, NULL);
-}
-
 static void WINAPI wine_vkDestroyValidationCacheEXT(VkDevice device, VkValidationCacheEXT validationCache, const VkAllocationCallbacks *pAllocator)
 {
     TRACE("%p, 0x%s, %p\n", device, wine_dbgstr_longlong(validationCache), pAllocator);
@@ -4348,12 +4276,6 @@ static VkResult WINAPI wine_vkGetPhysicalDeviceSupportedFramebufferMixedSamplesC
 #endif
 }
 
-VkResult WINAPI wine_vkGetPhysicalDeviceSurfaceCapabilitiesKHR(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, VkSurfaceCapabilitiesKHR *pSurfaceCapabilities)
-{
-    TRACE("%p, 0x%s, %p\n", physicalDevice, wine_dbgstr_longlong(surface), pSurfaceCapabilities);
-    return physicalDevice->instance->funcs.p_vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physicalDevice->phys_dev, surface, pSurfaceCapabilities);
-}
-
 static VkResult WINAPI wine_vkGetPhysicalDeviceSurfaceFormats2KHR(VkPhysicalDevice physicalDevice, const VkPhysicalDeviceSurfaceInfo2KHR *pSurfaceInfo, uint32_t *pSurfaceFormatCount, VkSurfaceFormat2KHR *pSurfaceFormats)
 {
 #if defined(USE_STRUCT_CONVERSION)
@@ -4477,12 +4399,6 @@ static VkResult WINAPI wine_vkGetShaderInfoAMD(VkDevice device, VkPipeline pipel
     return device->funcs.p_vkGetShaderInfoAMD(device->device, pipeline, shaderStage, infoType, pInfoSize, pInfo);
 }
 
-VkResult WINAPI wine_vkGetSwapchainImagesKHR(VkDevice device, VkSwapchainKHR swapchain, uint32_t *pSwapchainImageCount, VkImage *pSwapchainImages)
-{
-    TRACE("%p, 0x%s, %p, %p\n", device, wine_dbgstr_longlong(swapchain), pSwapchainImageCount, pSwapchainImages);
-    return device->funcs.p_vkGetSwapchainImagesKHR(device->device, swapchain, pSwapchainImageCount, pSwapchainImages);
-}
-
 static VkResult WINAPI wine_vkGetValidationCacheDataEXT(VkDevice device, VkValidationCacheEXT validationCache, size_t *pDataSize, void *pData)
 {
     TRACE("%p, 0x%s, %p, %p\n", device, wine_dbgstr_longlong(validationCache), pDataSize, pData);
@@ -4549,12 +4465,6 @@ VkResult WINAPI wine_vkQueueBindSparse(VkQueue queue, uint32_t bindInfoCount, co
 #endif
 }
 
-VkResult WINAPI wine_vkQueuePresentKHR(VkQueue queue, const VkPresentInfoKHR *pPresentInfo)
-{
-    TRACE("%p, %p\n", queue, pPresentInfo);
-    return queue->device->funcs.p_vkQueuePresentKHR(queue->queue, pPresentInfo);
-}
-
 static VkResult WINAPI wine_vkQueueSetPerformanceConfigurationINTEL(VkQueue queue, VkPerformanceConfigurationINTEL configuration)
 {
     TRACE("%p, 0x%s\n", queue, wine_dbgstr_longlong(configuration));
diff --git a/dlls/winevulkan/vulkan_thunks.h b/dlls/winevulkan/vulkan_thunks.h
index 01c1efb277..a262f01388 100644
--- a/dlls/winevulkan/vulkan_thunks.h
+++ b/dlls/winevulkan/vulkan_thunks.h
@@ -41,13 +41,17 @@
 #define WINE_VK_VERSION VK_API_VERSION_1_1
 
 /* Functions for which we have custom implementations outside of the thunks. */
+VkResult WINAPI wine_vkAcquireNextImageKHR(VkDevice device, VkSwapchainKHR swapchain, uint64_t timeout, VkSemaphore semaphore, VkFence fence, uint32_t *pImageIndex);
 VkResult WINAPI wine_vkAllocateCommandBuffers(VkDevice device, const VkCommandBufferAllocateInfo *pAllocateInfo, VkCommandBuffer *pCommandBuffers);
 void WINAPI wine_vkCmdExecuteCommands(VkCommandBuffer commandBuffer, uint32_t commandBufferCount, const VkCommandBuffer *pCommandBuffers);
+void WINAPI wine_vkCmdPipelineBarrier(VkCommandBuffer commandBuffer, VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask, VkDependencyFlags dependencyFlags, uint32_t memoryBarrierCount, const VkMemoryBarrier *pMemoryBarriers, uint32_t bufferMemoryBarrierCount, const VkBufferMemoryBarrier *pBufferMemoryBarriers, uint32_t imageMemoryBarrierCount, const VkImageMemoryBarrier *pImageMemoryBarriers);
 VkResult WINAPI wine_vkCreateCommandPool(VkDevice device, const VkCommandPoolCreateInfo *pCreateInfo, const VkAllocationCallbacks *pAllocator, VkCommandPool *pCommandPool);
 VkResult WINAPI wine_vkCreateDevice(VkPhysicalDevice physicalDevice, const VkDeviceCreateInfo *pCreateInfo, const VkAllocationCallbacks *pAllocator, VkDevice *pDevice);
+VkResult WINAPI wine_vkCreateSwapchainKHR(VkDevice device, const VkSwapchainCreateInfoKHR *pCreateInfo, const VkAllocationCallbacks *pAllocator, VkSwapchainKHR *pSwapchain);
 void WINAPI wine_vkDestroyCommandPool(VkDevice device, VkCommandPool commandPool, const VkAllocationCallbacks *pAllocator);
 void WINAPI wine_vkDestroyDevice(VkDevice device, const VkAllocationCallbacks *pAllocator);
 void WINAPI wine_vkDestroyInstance(VkInstance instance, const VkAllocationCallbacks *pAllocator);
+void WINAPI wine_vkDestroySwapchainKHR(VkDevice device, VkSwapchainKHR swapchain, const VkAllocationCallbacks *pAllocator);
 VkResult WINAPI wine_vkEnumerateDeviceExtensionProperties(VkPhysicalDevice physicalDevice, const char *pLayerName, uint32_t *pPropertyCount, VkExtensionProperties *pProperties);
 VkResult WINAPI wine_vkEnumeratePhysicalDeviceGroups(VkInstance instance, uint32_t *pPhysicalDeviceGroupCount, VkPhysicalDeviceGroupProperties *pPhysicalDeviceGroupProperties);
 VkResult WINAPI wine_vkEnumeratePhysicalDeviceGroupsKHR(VkInstance instance, uint32_t *pPhysicalDeviceGroupCount, VkPhysicalDeviceGroupProperties *pPhysicalDeviceGroupProperties) DECLSPEC_HIDDEN;
@@ -64,6 +68,9 @@ void WINAPI wine_vkGetPhysicalDeviceExternalSemaphoreProperties(VkPhysicalDevice
 void WINAPI wine_vkGetPhysicalDeviceExternalSemaphorePropertiesKHR(VkPhysicalDevice physicalDevice, const VkPhysicalDeviceExternalSemaphoreInfo *pExternalSemaphoreInfo, VkExternalSemaphoreProperties *pExternalSemaphoreProperties) DECLSPEC_HIDDEN;
 VkResult WINAPI wine_vkGetPhysicalDeviceImageFormatProperties2(VkPhysicalDevice physicalDevice, const VkPhysicalDeviceImageFormatInfo2 *pImageFormatInfo, VkImageFormatProperties2 *pImageFormatProperties);
 VkResult WINAPI wine_vkGetPhysicalDeviceImageFormatProperties2KHR(VkPhysicalDevice physicalDevice, const VkPhysicalDeviceImageFormatInfo2 *pImageFormatInfo, VkImageFormatProperties2 *pImageFormatProperties) DECLSPEC_HIDDEN;
+VkResult WINAPI wine_vkGetPhysicalDeviceSurfaceCapabilitiesKHR(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, VkSurfaceCapabilitiesKHR *pSurfaceCapabilities);
+VkResult WINAPI wine_vkGetSwapchainImagesKHR(VkDevice device, VkSwapchainKHR swapchain, uint32_t *pSwapchainImageCount, VkImage *pSwapchainImages);
+VkResult WINAPI wine_vkQueuePresentKHR(VkQueue queue, const VkPresentInfoKHR *pPresentInfo);
 VkResult WINAPI wine_vkQueueSubmit(VkQueue queue, uint32_t submitCount, const VkSubmitInfo *pSubmits, VkFence fence);
 
 /* Private thunks */
@@ -824,6 +831,10 @@ void free_VkDeviceCreateInfo_struct_chain(VkDeviceCreateInfo *s) DECLSPEC_HIDDEN
 VkResult convert_VkInstanceCreateInfo_struct_chain(const void *pNext, VkInstanceCreateInfo *out_struct) DECLSPEC_HIDDEN;
 void free_VkInstanceCreateInfo_struct_chain(VkInstanceCreateInfo *s) DECLSPEC_HIDDEN;
 
+VkBufferMemoryBarrier_host *convert_VkBufferMemoryBarrier_array_win_to_host(const VkBufferMemoryBarrier *in, uint32_t count);
+void free_VkBufferMemoryBarrier_array(VkBufferMemoryBarrier_host *in, uint32_t count);
+VkImageMemoryBarrier_host *convert_VkImageMemoryBarrier_array_win_to_host(const VkImageMemoryBarrier *in, uint32_t count);
+void free_VkImageMemoryBarrier_array(VkImageMemoryBarrier_host *in, uint32_t count);
 /* For use by vkDevice and children */
 struct vulkan_device_funcs
 {
diff --git a/dlls/winevulkan/winevulkan.spec b/dlls/winevulkan/winevulkan.spec
index 8cdf387857..e5614b65d9 100644
--- a/dlls/winevulkan/winevulkan.spec
+++ b/dlls/winevulkan/winevulkan.spec
@@ -37,6 +37,11 @@
 @ stdcall -private vk_icdGetInstanceProcAddr(ptr str) wine_vk_icdGetInstanceProcAddr
 @ stdcall -private vk_icdNegotiateLoaderICDInterfaceVersion(ptr) wine_vk_icdNegotiateLoaderICDInterfaceVersion
 @ cdecl -norelay native_vkGetInstanceProcAddrWINE(ptr str)
+@ stdcall __wine_get_native_VkDevice(ptr)
+@ stdcall __wine_get_native_VkInstance(ptr)
+@ stdcall __wine_get_native_VkPhysicalDevice(ptr)
+@ stdcall __wine_get_wrapped_VkPhysicalDevice(ptr)
+@ stdcall __wine_get_native_VkQueue(ptr)
 @ stdcall -private wine_vkAcquireNextImage2KHR(ptr ptr ptr)
 @ stdcall -private wine_vkAcquireNextImageKHR(ptr int64 int64 int64 int64 ptr)
 @ stdcall -private wine_vkAllocateCommandBuffers(ptr ptr ptr)
diff --git a/dlls/winex11.drv/clipboard.c b/dlls/winex11.drv/clipboard.c
index c51954f1bf..a37530fdbf 100644
--- a/dlls/winex11.drv/clipboard.c
+++ b/dlls/winex11.drv/clipboard.c
@@ -1849,12 +1849,13 @@ static BOOL request_selection_contents( Display *display, BOOL changed )
                last_size != size ||
                memcmp( last_data, data, size ));
 
-    if (!changed || !OpenClipboard( clipboard_hwnd ))
+    if (!changed)
     {
         HeapFree( GetProcessHeap(), 0, data );
         return FALSE;
     }
 
+    if (!OpenClipboard( clipboard_hwnd )) return FALSE;
     TRACE( "selection changed, importing\n" );
     EmptyClipboard();
     is_clipboard_owner = TRUE;
diff --git a/dlls/winex11.drv/desktop.c b/dlls/winex11.drv/desktop.c
index d70b38b2b3..79ba54b584 100644
--- a/dlls/winex11.drv/desktop.c
+++ b/dlls/winex11.drv/desktop.c
@@ -336,18 +336,52 @@ static BOOL CALLBACK update_windows_on_desktop_resize( HWND hwnd, LPARAM lparam
 
     if (!(data = get_win_data( hwnd ))) return TRUE;
 
-    /* update the full screen state */
-    update_net_wm_states( data );
+    if (fs_hack_mapping_required() &&
+            fs_hack_matches_current_mode(
+                data->whole_rect.right - data->whole_rect.left,
+                data->whole_rect.bottom - data->whole_rect.top)){
+        if(!data->fs_hack){
+            POINT p = fs_hack_real_mode();
+            POINT tl = virtual_screen_to_root(0, 0);
+            TRACE("Enabling fs hack, resizing window %p to (%u,%u)-(%u,%u)\n", hwnd, tl.x, tl.y, p.x, p.y);
+            data->fs_hack = TRUE;
+            set_wm_hints( data );
+            XMoveResizeWindow(data->display, data->whole_window, tl.x, tl.y, p.x, p.y);
+            if(data->client_window)
+                XMoveResizeWindow(data->display, data->client_window, 0, 0, p.x, p.y);
+            sync_gl_drawable(hwnd, FALSE);
+            update_net_wm_states( data );
+        }
+    }else {
 
-    if (resize_data->old_virtual_rect.left != resize_data->new_virtual_rect.left) mask |= CWX;
-    if (resize_data->old_virtual_rect.top != resize_data->new_virtual_rect.top) mask |= CWY;
-    if (mask && data->whole_window)
-    {
-        POINT pos = virtual_screen_to_root( data->whole_rect.left, data->whole_rect.top );
-        XWindowChanges changes;
-        changes.x = pos.x;
-        changes.y = pos.y;
-        XReconfigureWMWindow( data->display, data->whole_window, data->vis.screen, mask, &changes );
+        /* update the full screen state */
+        update_net_wm_states( data );
+
+        if (resize_data->old_virtual_rect.left != resize_data->new_virtual_rect.left || data->fs_hack) mask |= CWX;
+        if (resize_data->old_virtual_rect.top != resize_data->new_virtual_rect.top || data->fs_hack) mask |= CWY;
+        if (mask && data->whole_window)
+        {
+            POINT pos = virtual_screen_to_root( data->whole_rect.left, data->whole_rect.top );
+            XWindowChanges changes;
+            changes.x = pos.x;
+            changes.y = pos.y;
+            XReconfigureWMWindow( data->display, data->whole_window, data->vis.screen, mask, &changes );
+        }
+
+        if(data->fs_hack &&
+            !fs_hack_matches_current_mode(
+                data->whole_rect.right - data->whole_rect.left,
+                data->whole_rect.bottom - data->whole_rect.top)){
+            TRACE("Disabling fs hack\n");
+            data->fs_hack = FALSE;
+            if(data->client_window){
+                XMoveResizeWindow(data->display, data->client_window,
+                        data->client_rect.left, data->client_rect.top,
+                        data->client_rect.right - data->client_rect.left,
+                        data->client_rect.bottom - data->client_rect.top);
+            }
+            sync_gl_drawable(hwnd, FALSE);
+        }
     }
     release_win_data( data );
     if (hwnd == GetForegroundWindow()) clip_fullscreen_window( hwnd, TRUE );
@@ -410,7 +444,8 @@ void X11DRV_resize_desktop( unsigned int width, unsigned int height )
 
     if (GetWindowThreadProcessId( hwnd, NULL ) != GetCurrentThreadId())
     {
-        SendMessageW( hwnd, WM_X11DRV_RESIZE_DESKTOP, 0, MAKELPARAM( width, height ) );
+        POINT new_mode = fs_hack_current_mode();
+        SendMessageW( hwnd, WM_X11DRV_RESIZE_DESKTOP, MAKEWPARAM(new_mode.x, new_mode.y), MAKELPARAM( width, height ) );
     }
     else
     {
diff --git a/dlls/winex11.drv/display.c b/dlls/winex11.drv/display.c
index 7ad74c9b4e..9b56727eb2 100644
--- a/dlls/winex11.drv/display.c
+++ b/dlls/winex11.drv/display.c
@@ -30,6 +30,7 @@
 #include "initguid.h"
 #include "devguid.h"
 #include "devpkey.h"
+#include "ntddvdeo.h"
 #include "setupapi.h"
 #define WIN32_NO_STATUS
 #include "winternl.h"
@@ -271,11 +272,71 @@ void X11DRV_DisplayDevices_RegisterEventHandlers(void)
         handler->register_event_handlers();
 }
 
+/* This function sets device interface link state to enabled.
+ * The link state should be set via IoSetDeviceInterfaceState(),
+ * but IoSetDeviceInterfaceState() requires a PnP driver, which
+ * currently doesn't exist for display devices. */
+static BOOL link_device(const WCHAR *instance, const GUID *guid)
+{
+    static const WCHAR device_instanceW[] = {'D','e','v','i','c','e','I','n','s','t','a','n','c','e',0};
+    static const WCHAR hash_controlW[] = {'#','\\','C','o','n','t','r','o','l',0};
+    static const WCHAR linkedW[] = {'L','i','n','k','e','d',0};
+    static const DWORD enabled = 1;
+    WCHAR device_key_name[MAX_PATH], device_instance[MAX_PATH];
+    HKEY iface_key, device_key, control_key;
+    DWORD length, type, index = 0;
+    BOOL ret = FALSE;
+    LSTATUS lr;
+
+    iface_key = SetupDiOpenClassRegKeyExW(guid, KEY_ALL_ACCESS, DIOCR_INTERFACE, NULL, NULL);
+    while (1)
+    {
+        length = ARRAY_SIZE(device_key_name);
+        lr = RegEnumKeyExW(iface_key, index++, device_key_name, &length, NULL, NULL, NULL, NULL);
+        if (lr)
+            break;
+
+        lr = RegOpenKeyExW(iface_key, device_key_name, 0, KEY_ALL_ACCESS, &device_key);
+        if (lr)
+            continue;
+
+        length = ARRAY_SIZE(device_instance);
+        lr = RegQueryValueExW(device_key, device_instanceW, NULL, &type, (BYTE *)device_instance, &length);
+        if (lr || type != REG_SZ)
+        {
+            RegCloseKey(device_key);
+            continue;
+        }
+
+        if (lstrcmpiW(device_instance, instance))
+        {
+            RegCloseKey(device_key);
+            continue;
+        }
+
+        lr = RegCreateKeyExW(device_key, hash_controlW, 0, NULL, REG_OPTION_VOLATILE, KEY_ALL_ACCESS, NULL, &control_key, NULL);
+        RegCloseKey(device_key);
+        if (lr)
+            continue;
+
+        lr = RegSetValueExW(control_key, linkedW, 0, REG_DWORD, (const BYTE *)&enabled, sizeof(enabled));
+        RegCloseKey(control_key);
+        if (!lr)
+        {
+            ret = TRUE;
+            break;
+        }
+    }
+    RegCloseKey(iface_key);
+    return ret;
+}
+
 /* Initialize a GPU instance and return its GUID string in guid_string and driver value in driver parameter */
 static BOOL X11DRV_InitGpu(HDEVINFO devinfo, const struct x11drv_gpu *gpu, INT gpu_index, WCHAR *guid_string,
                            WCHAR *driver)
 {
     static const BOOL present = TRUE;
+    SP_DEVICE_INTERFACE_DATA iface_data = {sizeof(iface_data)};
     SP_DEVINFO_DATA device_data = {sizeof(device_data)};
     WCHAR instanceW[MAX_PATH];
     WCHAR bufferW[1024];
@@ -294,6 +355,13 @@ static BOOL X11DRV_InitGpu(HDEVINFO devinfo, const struct x11drv_gpu *gpu, INT g
             goto done;
     }
 
+    /* Register GUID_DEVINTERFACE_DISPLAY_ADAPTER */
+    if (!SetupDiCreateDeviceInterfaceW(devinfo, &device_data, &GUID_DEVINTERFACE_DISPLAY_ADAPTER, NULL, 0, &iface_data))
+        goto done;
+
+    if (!link_device(instanceW, &GUID_DEVINTERFACE_DISPLAY_ADAPTER))
+        goto done;
+
     /* Write HardwareID registry property, REG_MULTI_SZ */
     written = sprintfW(bufferW, gpu_hardware_id_fmtW, gpu->vendor_id, gpu->device_id);
     bufferW[written + 1] = 0;
@@ -420,6 +488,7 @@ done:
 static BOOL X11DRV_InitMonitor(HDEVINFO devinfo, const struct x11drv_monitor *monitor, int monitor_index,
                                int video_index)
 {
+    SP_DEVICE_INTERFACE_DATA iface_data = {sizeof(iface_data)};
     SP_DEVINFO_DATA device_data = {sizeof(SP_DEVINFO_DATA)};
     WCHAR bufferW[MAX_PATH];
     HKEY hkey;
@@ -431,6 +500,13 @@ static BOOL X11DRV_InitMonitor(HDEVINFO devinfo, const struct x11drv_monitor *mo
     if (!SetupDiRegisterDeviceInfo(devinfo, &device_data, 0, NULL, NULL, NULL))
         goto done;
 
+    /* Register GUID_DEVINTERFACE_MONITOR */
+    if (!SetupDiCreateDeviceInterfaceW(devinfo, &device_data, &GUID_DEVINTERFACE_MONITOR, NULL, 0, &iface_data))
+        goto done;
+
+    if (!link_device(bufferW, &GUID_DEVINTERFACE_MONITOR))
+        goto done;
+
     /* Write HardwareID registry property */
     if (!SetupDiSetDeviceRegistryPropertyW(devinfo, &device_data, SPDRP_HARDWAREID,
                                            (const BYTE *)monitor_hardware_idW, sizeof(monitor_hardware_idW)))
diff --git a/dlls/winex11.drv/event.c b/dlls/winex11.drv/event.c
index 412635d4f5..1f7c09666c 100644
--- a/dlls/winex11.drv/event.c
+++ b/dlls/winex11.drv/event.c
@@ -821,9 +821,27 @@ static void focus_out( Display *display , HWND hwnd )
     Window focus_win;
     int revert;
     XIC xic;
+    struct x11drv_win_data *data;
 
     if (ximInComposeMode) return;
 
+    data = get_win_data(hwnd);
+    if(data){
+        ULONGLONG now = GetTickCount64();
+        if(data->take_focus_back > 0 &&
+                now >= data->take_focus_back &&
+                now - data->take_focus_back < 1000){
+            data->take_focus_back = 0;
+            TRACE("workaround mutter bug, taking focus back\n");
+            XSetInputFocus( data->display, data->whole_window, RevertToParent, CurrentTime);
+            release_win_data(data);
+            /* don't inform win32 client */
+            return;
+        }
+        data->take_focus_back = 0;
+        release_win_data(data);
+    }
+
     x11drv_thread_data()->last_focus = hwnd;
     if ((xic = X11DRV_get_ic( hwnd ))) XUnsetICFocus( xic );
 
@@ -1124,6 +1142,12 @@ static BOOL X11DRV_ConfigureNotify( HWND hwnd, XEvent *xev )
                event->serial, data->configure_serial );
         goto done;
     }
+    if (data->pending_fullscreen)
+    {
+        TRACE( "win %p/%lx event %d,%d,%dx%d pending_fullscreen is pending, so ignoring\n",
+               hwnd, data->whole_window, event->x, event->y, event->width, event->height );
+        goto done;
+    }
 
     /* Get geometry */
 
@@ -1145,8 +1169,16 @@ static BOOL X11DRV_ConfigureNotify( HWND hwnd, XEvent *xev )
     }
     else pos = root_to_virtual_screen( x, y );
 
-    X11DRV_X_to_window_rect( data, &rect, pos.x, pos.y, event->width, event->height );
-    if (root_coords) MapWindowPoints( 0, parent, (POINT *)&rect, 2 );
+    if(data->fs_hack){
+        POINT p = fs_hack_current_mode();
+        rect.left = 0;
+        rect.top = 0;
+        rect.right = p.x;
+        rect.bottom = p.y;
+    }else{
+        X11DRV_X_to_window_rect( data, &rect, pos.x, pos.y, event->width, event->height );
+        if (root_coords) MapWindowPoints( 0, parent, (POINT *)&rect, 2 );
+    }
 
     TRACE( "win %p/%lx new X rect %d,%d,%dx%d (event %d,%d,%dx%d)\n",
            hwnd, data->whole_window, rect.left, rect.top, rect.right-rect.left, rect.bottom-rect.top,
@@ -1154,6 +1186,19 @@ static BOOL X11DRV_ConfigureNotify( HWND hwnd, XEvent *xev )
 
     /* Compare what has changed */
 
+    {
+        const char *steamgameid = getenv("SteamGameId");
+        if(steamgameid && !strcmp(steamgameid, "590380")){
+            /* Into The Breach is extremely picky about the size of its window. */
+            if(is_window_rect_fullscreen(&data->whole_rect) &&
+                    is_window_rect_fullscreen(&rect)){
+                TRACE("window is fullscreen and new size is also fullscreen, so preserving window size\n");
+                rect.right = rect.left + (data->whole_rect.right - data->whole_rect.left);
+                rect.bottom = rect.top + (data->whole_rect.bottom - data->whole_rect.top);
+            }
+        }
+    }
+
     x     = rect.left;
     y     = rect.top;
     cx    = rect.right - rect.left;
@@ -1332,8 +1377,6 @@ static void handle_wm_state_notify( HWND hwnd, XPropertyEvent *event, BOOL updat
             {
                 TRACE( "restoring win %p/%lx\n", data->hwnd, data->whole_window );
                 release_win_data( data );
-                if ((style & (WS_MINIMIZE | WS_VISIBLE)) == (WS_MINIMIZE | WS_VISIBLE))
-                    SetActiveWindow( hwnd );
                 SendMessageW( hwnd, WM_SYSCOMMAND, SC_RESTORE, 0 );
                 return;
             }
@@ -1357,15 +1400,57 @@ done:
 }
 
 
+static void handle__net_wm_state_notify( HWND hwnd, XPropertyEvent *event )
+{
+    struct x11drv_win_data *data = get_win_data( hwnd );
+
+    if(data->pending_fullscreen)
+    {
+        read_net_wm_states( event->display, data );
+        if(data->net_wm_state & (1 << NET_WM_STATE_FULLSCREEN)){
+            data->pending_fullscreen = FALSE;
+            TRACE("PropertyNotify _NET_WM_STATE, now 0x%x, pending_fullscreen no longer pending.\n",
+                    data->net_wm_state);
+        }else
+            TRACE("PropertyNotify _NET_WM_STATE, now 0x%x, pending_fullscreen still pending.\n",
+                    data->net_wm_state);
+    }
+
+    release_win_data( data );
+}
+
+
 /***********************************************************************
  *           X11DRV_PropertyNotify
  */
 static BOOL X11DRV_PropertyNotify( HWND hwnd, XEvent *xev )
 {
     XPropertyEvent *event = &xev->xproperty;
+    char *name;
 
     if (!hwnd) return FALSE;
+
+    name = XGetAtomName(event->display, event->atom);
+    if(name){
+        TRACE("win %p PropertyNotify atom: %s, state: 0x%x\n", hwnd, name, event->state);
+        XFree(name);
+    }
+
+    if (event->atom == x11drv_atom(_NET_WM_BYPASS_COMPOSITOR))
+    {
+        struct x11drv_win_data *data = get_win_data( hwnd );
+        if (!data) return TRUE;
+
+        /* workaround for mutter gitlab bug #676, changing decorations of a
+         * fullscreen and unredirected window freezes the compositing.
+         */
+        if (wm_is_mutter( data->display )) set_wm_hints( data );
+
+        release_win_data( data );
+    }
+
     if (event->atom == x11drv_atom(WM_STATE)) handle_wm_state_notify( hwnd, event, TRUE );
+    else if (event->atom == x11drv_atom(_NET_WM_STATE)) handle__net_wm_state_notify( hwnd, event );
     return TRUE;
 }
 
@@ -1512,6 +1597,7 @@ static void EVENT_DropFromOffiX( HWND hWnd, XClientMessageEvent *event )
     Window		win, w_aux_root, w_aux_child;
 
     if (!(data = get_win_data( hWnd ))) return;
+    ERR("TODO: fs hack\n");
     cx = data->whole_rect.right - data->whole_rect.left;
     cy = data->whole_rect.bottom - data->whole_rect.top;
     win = data->whole_window;
diff --git a/dlls/winex11.drv/graphics.c b/dlls/winex11.drv/graphics.c
index 77ca60ec44..979e7fddd3 100644
--- a/dlls/winex11.drv/graphics.c
+++ b/dlls/winex11.drv/graphics.c
@@ -251,6 +251,7 @@ static void update_x11_clipping( X11DRV_PDEVICE *physDev, HRGN rgn )
     }
     else if ((data = X11DRV_GetRegionData( rgn, 0 )))
     {
+        fs_hack_rgndata_user_to_real(data);
         XSetClipRectangles( gdi_display, physDev->gc, physDev->dc_rect.left, physDev->dc_rect.top,
                             (XRectangle *)data->Buffer, data->rdh.nCount, YXBanded );
         HeapFree( GetProcessHeap(), 0, data );
diff --git a/dlls/winex11.drv/mouse.c b/dlls/winex11.drv/mouse.c
index 7f0edd9be6..8f8cc4a0a9 100644
--- a/dlls/winex11.drv/mouse.c
+++ b/dlls/winex11.drv/mouse.c
@@ -260,6 +260,8 @@ static void update_relative_valuators(XIAnyClassInfo **valuators, int n_valuator
 
     thread_data->x_rel_valuator.number = -1;
     thread_data->y_rel_valuator.number = -1;
+    thread_data->x_rel_valuator.accum = 0;
+    thread_data->y_rel_valuator.accum = 0;
 
     for (i = 0; i < n_valuators; i++)
     {
@@ -365,6 +367,8 @@ static void disable_xinput2(void)
     pXIFreeDeviceInfo( data->xi2_devices );
     data->x_rel_valuator.number = -1;
     data->y_rel_valuator.number = -1;
+    data->x_rel_valuator.accum = 0;
+    data->y_rel_valuator.accum = 0;
     data->xi2_devices = NULL;
     data->xi2_core_pointer = 0;
     data->xi2_current_slave = 0;
@@ -384,6 +388,7 @@ static BOOL grab_clipping_window( const RECT *clip )
     Window clip_window;
     HWND msg_hwnd = 0;
     POINT pos;
+    RECT real_clip;
 
     if (GetWindowThreadProcessId( GetDesktopWindow(), NULL ) == GetCurrentThreadId())
         return TRUE;  /* don't clip in the desktop process */
@@ -422,9 +427,28 @@ static BOOL grab_clipping_window( const RECT *clip )
     TRACE( "clipping to %s win %lx\n", wine_dbgstr_rect(clip), clip_window );
 
     if (!data->clip_hwnd) XUnmapWindow( data->display, clip_window );
-    pos = virtual_screen_to_root( clip->left, clip->top );
+
+    pos.x = clip->left;
+    pos.y = clip->top;
+    fs_hack_user_to_real(&pos);
+    real_clip.left = pos.x;
+    real_clip.top = pos.y;
+
+    pos.x = clip->right;
+    pos.y = clip->bottom;
+    fs_hack_user_to_real(&pos);
+    real_clip.right = pos.x;
+    real_clip.bottom = pos.y;
+
+    pos = virtual_screen_to_root( real_clip.left, real_clip.top );
+
+    TRACE("setting real clip to %d,%d x %d,%d\n",
+            pos.x, pos.y,
+            real_clip.right - real_clip.left,
+            real_clip.bottom - real_clip.top);
+
     XMoveResizeWindow( data->display, clip_window, pos.x, pos.y,
-                       max( 1, clip->right - clip->left ), max( 1, clip->bottom - clip->top ) );
+                       max( 1, real_clip.right - real_clip.left ), max( 1, real_clip.bottom - real_clip.top ) );
     XMapWindow( data->display, clip_window );
 
     /* if the rectangle is shrinking we may get a pointer warp */
@@ -565,8 +589,10 @@ BOOL clip_fullscreen_window( HWND hwnd, BOOL reset )
     release_win_data( data );
     if (!fullscreen) return FALSE;
     if (!(thread_data = x11drv_thread_data())) return FALSE;
-    if (GetTickCount() - thread_data->clip_reset < 1000) return FALSE;
-    if (!reset && clipping_cursor && thread_data->clip_hwnd) return FALSE;  /* already clipping */
+    if (!reset) {
+        if (GetTickCount() - thread_data->clip_reset < 1000) return FALSE;
+        if (clipping_cursor && thread_data->clip_hwnd) return FALSE;  /* already clipping */
+    }
     rect = get_primary_monitor_rect();
     if (!grab_fullscreen)
     {
@@ -618,8 +644,18 @@ static void send_mouse_input( HWND hwnd, Window window, unsigned int state, INPU
             sync_window_cursor( window );
             last_cursor_change = input->u.mi.time;
         }
-        input->u.mi.dx += clip_rect.left;
-        input->u.mi.dy += clip_rect.top;
+
+        pt.x = clip_rect.left;
+        pt.y = clip_rect.top;
+        fs_hack_user_to_real(&pt);
+
+        pt.x += input->u.mi.dx;
+        pt.y += input->u.mi.dy;
+        fs_hack_real_to_user(&pt);
+
+        input->u.mi.dx = pt.x;
+        input->u.mi.dy = pt.y;
+
         __wine_send_input( hwnd, input );
         return;
     }
@@ -633,7 +669,13 @@ static void send_mouse_input( HWND hwnd, Window window, unsigned int state, INPU
 
     if (!(data = get_win_data( hwnd ))) return;
 
-    if (window == data->whole_window)
+    if(data->fs_hack)
+        fs_hack_real_to_user(&pt);
+
+    input->u.mi.dx = pt.x;
+    input->u.mi.dy = pt.y;
+
+    if (window == data->whole_window && !data->fs_hack)
     {
         pt.x += data->whole_rect.left - data->client_rect.left;
         pt.y += data->whole_rect.top - data->client_rect.top;
@@ -1467,7 +1509,13 @@ void CDECL X11DRV_SetCursor( HCURSOR handle )
 BOOL CDECL X11DRV_SetCursorPos( INT x, INT y )
 {
     struct x11drv_thread_data *data = x11drv_init_thread_data();
-    POINT pos = virtual_screen_to_root( x, y );
+    POINT pos = {x, y};
+
+    fs_hack_user_to_real(&pos);
+    pos = virtual_screen_to_root( pos.x, pos.y );
+
+    TRACE("real setting to %u, %u\n",
+            pos.x, pos.y);
 
     if (keyboard_grabbed)
     {
@@ -1492,7 +1540,7 @@ BOOL CDECL X11DRV_SetCursorPos( INT x, INT y )
 
     XNoOp( data->display );
     XFlush( data->display ); /* avoids bad mouse lag in games that do their own mouse warping */
-    TRACE( "warped to %d,%d serial %lu\n", x, y, data->warp_serial );
+    TRACE( "warped to (fake) %d,%d serial %lu\n", x, y, data->warp_serial );
     return TRUE;
 }
 
@@ -1512,6 +1560,7 @@ BOOL CDECL X11DRV_GetCursorPos(LPPOINT pos)
     {
         POINT old = *pos;
         *pos = root_to_virtual_screen( winX, winY );
+        fs_hack_real_to_user(pos);
         TRACE( "pointer at %s server pos %s\n", wine_dbgstr_point(pos), wine_dbgstr_point(&old) );
     }
     return ret;
@@ -1541,17 +1590,18 @@ BOOL CDECL X11DRV_ClipCursor( LPCRECT clip )
         }
 
         /* we are clipping if the clip rectangle is smaller than the screen */
-        if (clip->left > virtual_rect.left || clip->right < virtual_rect.right ||
-            clip->top > virtual_rect.top || clip->bottom < virtual_rect.bottom)
+        if (!(!fs_hack_enabled() && clip->left == 0 && clip->top == 0 && fs_hack_matches_last_mode(clip->right, clip->bottom)) && /* fix games trying to reset clip to full screen */
+                (clip->left > virtual_rect.left || clip->right < virtual_rect.right ||
+                 clip->top > virtual_rect.top || clip->bottom < virtual_rect.bottom))
         {
             if (grab_clipping_window( clip )) return TRUE;
         }
-        else /* if currently clipping, check if we should switch to fullscreen clipping */
+        else /* check if we should switch to fullscreen clipping */
         {
             struct x11drv_thread_data *data = x11drv_thread_data();
-            if (data && data->clip_hwnd)
+            if (data)
             {
-                if (EqualRect( clip, &clip_rect ) || clip_fullscreen_window( foreground, TRUE ))
+                if ((data->clip_hwnd && EqualRect( clip, &clip_rect ) && !EqualRect(&clip_rect, &virtual_rect)) || clip_fullscreen_window( foreground, TRUE ))
                     return TRUE;
             }
         }
@@ -1784,6 +1834,7 @@ static BOOL X11DRV_RawMotion( XGenericEventCookie *xev )
     const double *values = event->valuators.values;
     RECT virtual_rect;
     INPUT input;
+    POINT pt;
     int i;
     double dx = 0, dy = 0, val;
     struct x11drv_thread_data *thread_data = x11drv_thread_data();
@@ -1827,31 +1878,53 @@ static BOOL X11DRV_RawMotion( XGenericEventCookie *xev )
 
     for (i = 0; i <= max ( x_rel->number, y_rel->number ); i++)
     {
-        if (!XIMaskIsSet( event->valuators.mask, i )) continue;
+        if (!XIMaskIsSet( event->valuators.mask, i ))
+            continue;
         val = *values++;
         if (i == x_rel->number)
         {
-            input.u.mi.dx = dx = val;
+            dx = val;
             if (x_rel->min < x_rel->max)
-                input.u.mi.dx = val * (virtual_rect.right - virtual_rect.left)
-                                    / (x_rel->max - x_rel->min);
+                dx = val * (virtual_rect.right - virtual_rect.left)
+                         / (x_rel->max - x_rel->min);
         }
         if (i == y_rel->number)
         {
-            input.u.mi.dy = dy = val;
+            dy = val;
             if (y_rel->min < y_rel->max)
-                input.u.mi.dy = val * (virtual_rect.bottom - virtual_rect.top)
-                                    / (y_rel->max - y_rel->min);
+                dy = val * (virtual_rect.bottom - virtual_rect.top)
+                         / (y_rel->max - y_rel->min);
         }
     }
 
+    /* Accumulate the *double* dx/dy motions so sub-pixel motions wont be lost
+     * when sent/cast to *LONG* input.u.mi.dx/dy.
+     */
+    x_rel->accum += dx;
+    y_rel->accum += dy;
+    if (fabs(x_rel->accum) < 1.0 && fabs(y_rel->accum) < 1.0)
+    {
+        TRACE( "accumulating raw motion (event %f,%f, accum %f,%f)\n", dx, dy, x_rel->accum, y_rel->accum );
+        return TRUE;
+    }
+    input.u.mi.dx = x_rel->accum;
+    input.u.mi.dy = y_rel->accum;
+    x_rel->accum -= input.u.mi.dx;
+    y_rel->accum -= input.u.mi.dy;
+
     if (broken_rawevents && is_old_motion_event( xev->serial ))
     {
         TRACE( "pos %d,%d old serial %lu, ignoring\n", input.u.mi.dx, input.u.mi.dy, xev->serial );
         return FALSE;
     }
 
-    TRACE( "pos %d,%d (event %f,%f)\n", input.u.mi.dx, input.u.mi.dy, dx, dy );
+    pt.x = input.u.mi.dx;
+    pt.y = input.u.mi.dy;
+    fs_hack_scale_real_to_user(&pt);
+    input.u.mi.dx = pt.x;
+    input.u.mi.dy = pt.y;
+
+    TRACE( "pos %d,%d (event %f,%f, accum %f,%f)\n", input.u.mi.dx, input.u.mi.dy, dx, dy, x_rel->accum, y_rel->accum );
 
     input.type = INPUT_MOUSE;
     __wine_send_input( 0, &input );
diff --git a/dlls/winex11.drv/opengl.c b/dlls/winex11.drv/opengl.c
index 635c7d9f3b..5b5ebe2d6e 100644
--- a/dlls/winex11.drv/opengl.c
+++ b/dlls/winex11.drv/opengl.c
@@ -43,6 +43,10 @@
 #include "wine/library.h"
 #include "wine/debug.h"
 
+#ifndef ARRAY_SIZE
+#define ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0]))
+#endif
+
 #ifdef SONAME_LIBGL
 
 WINE_DEFAULT_DEBUG_CHANNEL(wgl);
@@ -206,6 +210,12 @@ struct wgl_context
     struct gl_drawable *drawables[2];
     struct gl_drawable *new_drawables[2];
     BOOL refresh_drawables;
+    BOOL fs_hack;
+    GLuint fs_hack_fbo, fs_hack_resolve_fbo;
+    GLuint fs_hack_color_texture, fs_hack_ds_texture;
+    GLuint fs_hack_color_renderbuffer, fs_hack_color_resolve_renderbuffer, fs_hack_ds_renderbuffer;
+    POINT setup_for;
+    GLuint current_draw_fbo, current_read_fbo;
     struct list entry;
 };
 
@@ -249,6 +259,10 @@ struct gl_drawable
     SIZE                           pixmap_size;  /* pixmap size for GLXPixmap drawables */
     int                            swap_interval;
     BOOL                           refresh_swap_interval;
+    BOOL                           fs_hack;
+    BOOL                           fs_hack_did_swapbuf;
+    BOOL                           fs_hack_context_set_up;
+    BOOL                           has_scissor_indexed;
 };
 
 enum glx_swap_control_method
@@ -372,6 +386,10 @@ static int   (*pglXSwapIntervalSGI)(int);
 static void* (*pglXAllocateMemoryNV)(GLsizei size, GLfloat readfreq, GLfloat writefreq, GLfloat priority);
 static void  (*pglXFreeMemoryNV)(GLvoid *pointer);
 
+static void (*pglScissorIndexed)(GLuint, GLint, GLint, GLsizei, GLsizei);
+static void (*pglScissorIndexedv)(GLuint, const GLint *);
+static void (*pglGetIntegeri_v)(GLenum, GLuint, GLint *);
+
 /* MESA GLX Extensions */
 static void (*pglXCopySubBufferMESA)(Display *dpy, GLXDrawable drawable, int x, int y, int width, int height);
 static int (*pglXSwapIntervalMESA)(unsigned int interval);
@@ -395,6 +413,27 @@ static void wglFinish(void);
 static void wglFlush(void);
 static const GLubyte *wglGetString(GLenum name);
 
+/* Fullscreen hack */
+static void (*pglBindFramebuffer)( GLenum target, GLuint framebuffer );
+static void (*pglBindFramebufferEXT)( GLenum target, GLuint framebuffer );
+static void (*pglBindRenderbuffer)( GLenum target, GLuint renderbuffer );
+static void (*pglBlitFramebuffer)( GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter );
+void (*pglDeleteFramebuffers)( GLsizei n, const GLuint *framebuffers );
+void (*pglDeleteRenderbuffers)( GLsizei n, const GLuint *renderbuffers );
+static void (*pglDrawBuffer)( GLenum buffer );
+static void (*pglFramebufferRenderbuffer)( GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer );
+static void (*pglFramebufferTexture2D)( GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level );
+static void (*pglGenFramebuffers)( GLsizei n, GLuint *ids );
+static void (*pglGenRenderbuffers)( GLsizei n, GLuint *renderbuffers );
+static void (*pglReadBuffer)( GLenum src );
+static void (*pglRenderbufferStorage)( GLenum target, GLenum internalformat, GLsizei width, GLsizei height );
+static void (*pglRenderbufferStorageMultisample)( GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height );
+
+static void wglBindFramebuffer( GLenum target, GLuint framebuffer );
+static void wglBindFramebufferEXT( GLenum target, GLuint framebuffer );
+static void wglDrawBuffer( GLenum buffer );
+static void wglReadBuffer( GLenum src );
+
 /* check if the extension is present in the list */
 static BOOL has_extension( const char *list, const char *ext )
 {
@@ -572,9 +611,11 @@ static BOOL WINAPI init_opengl( INIT_ONCE *once, void *param, void **context )
     /* redirect some standard OpenGL functions */
 #define REDIRECT(func) \
     do { p##func = opengl_funcs.gl.p_##func; opengl_funcs.gl.p_##func = w##func; } while(0)
+    REDIRECT( glDrawBuffer );
     REDIRECT( glFinish );
     REDIRECT( glFlush );
     REDIRECT( glGetString );
+    REDIRECT( glReadBuffer );
 #undef REDIRECT
 
     pglXGetProcAddressARB = wine_dlsym(opengl_handle, "glXGetProcAddressARB", NULL, 0);
@@ -583,6 +624,22 @@ static BOOL WINAPI init_opengl( INIT_ONCE *once, void *param, void **context )
         goto failed;
     }
 
+    /* Fullscreen hack */
+#define LOAD_FUNCPTR(func) p##func = (void *)pglXGetProcAddressARB((const unsigned char *)#func);
+    LOAD_FUNCPTR( glBindFramebuffer );
+    LOAD_FUNCPTR( glBindFramebufferEXT );
+    LOAD_FUNCPTR( glBindRenderbuffer );
+    LOAD_FUNCPTR( glBlitFramebuffer );
+    LOAD_FUNCPTR( glDeleteFramebuffers );
+    LOAD_FUNCPTR( glDeleteRenderbuffers );
+    LOAD_FUNCPTR( glFramebufferRenderbuffer );
+    LOAD_FUNCPTR( glFramebufferTexture2D );
+    LOAD_FUNCPTR( glGenFramebuffers );
+    LOAD_FUNCPTR( glGenRenderbuffers );
+    LOAD_FUNCPTR( glRenderbufferStorage );
+    LOAD_FUNCPTR( glRenderbufferStorageMultisample );
+#undef LOAD_FUNCPTR
+
 #define LOAD_FUNCPTR(f) do if((p##f = (void*)pglXGetProcAddressARB((const unsigned char*)#f)) == NULL) \
     { \
         ERR( "%s not found in libGL, disabling OpenGL.\n", #f ); \
@@ -633,6 +690,10 @@ static BOOL WINAPI init_opengl( INIT_ONCE *once, void *param, void **context )
     /* NV GLX Extension */
     LOAD_FUNCPTR(glXAllocateMemoryNV);
     LOAD_FUNCPTR(glXFreeMemoryNV);
+
+    LOAD_FUNCPTR(glScissorIndexed);
+    LOAD_FUNCPTR(glScissorIndexedv);
+    LOAD_FUNCPTR(glGetIntegeri_v);
 #undef LOAD_FUNCPTR
 
     if(!X11DRV_WineGL_InitOpenglInfo()) goto failed;
@@ -723,6 +784,13 @@ static BOOL WINAPI init_opengl( INIT_ONCE *once, void *param, void **context )
         pglXSwapBuffersMscOML = pglXGetProcAddressARB( (const GLubyte *)"glXSwapBuffersMscOML" );
     }
 
+    if (has_extension( glExtensions, "GL_ARB_viewport_array"))
+    {
+        opengl_funcs.ext.p_glGetIntegeri_v = pglGetIntegeri_v;
+        opengl_funcs.ext.p_glScissorIndexed = pglScissorIndexed;
+        opengl_funcs.ext.p_glScissorIndexedv = pglScissorIndexedv;
+    }
+
     X11DRV_WineGL_LoadExtensions();
     init_pixel_formats( gdi_display );
     return TRUE;
@@ -754,7 +822,7 @@ static const char *debugstr_fbconfig( GLXFBConfig fbconfig )
 static int ConvertAttribWGLtoGLX(const int* iWGLAttr, int* oGLXAttr, struct wgl_pbuffer* pbuf) {
   int nAttribs = 0;
   unsigned cur = 0; 
-  int attr, pop;
+  int pop;
   int drawattrib = 0;
   int nvfloatattrib = GLX_DONT_CARE;
   int pixelattrib = GLX_DONT_CARE;
@@ -762,53 +830,62 @@ static int ConvertAttribWGLtoGLX(const int* iWGLAttr, int* oGLXAttr, struct wgl_
   /* The list of WGL attributes is allowed to be NULL. We don't return here for NULL
    * because we need to do fixups for GLX_DRAWABLE_TYPE/GLX_RENDER_TYPE/GLX_FLOAT_COMPONENTS_NV. */
   while (iWGLAttr && 0 != iWGLAttr[cur]) {
-    attr = iWGLAttr[cur];
-    TRACE("pAttr[%d] = %x\n", cur, attr);
-    pop = iWGLAttr[++cur];
+    TRACE("pAttr[%d] = %x\n", cur, iWGLAttr[cur]);
 
-    switch (attr) {
+    switch (iWGLAttr[cur]) {
     case WGL_AUX_BUFFERS_ARB:
+      pop = iWGLAttr[++cur];
       PUSH2(oGLXAttr, GLX_AUX_BUFFERS, pop);
       TRACE("pAttr[%d] = GLX_AUX_BUFFERS: %d\n", cur, pop);
       break;
     case WGL_COLOR_BITS_ARB:
+      pop = iWGLAttr[++cur];
       PUSH2(oGLXAttr, GLX_BUFFER_SIZE, pop);
       TRACE("pAttr[%d] = GLX_BUFFER_SIZE: %d\n", cur, pop);
       break;
     case WGL_BLUE_BITS_ARB:
+      pop = iWGLAttr[++cur];
       PUSH2(oGLXAttr, GLX_BLUE_SIZE, pop);
       TRACE("pAttr[%d] = GLX_BLUE_SIZE: %d\n", cur, pop);
       break;
     case WGL_RED_BITS_ARB:
+      pop = iWGLAttr[++cur];
       PUSH2(oGLXAttr, GLX_RED_SIZE, pop);
       TRACE("pAttr[%d] = GLX_RED_SIZE: %d\n", cur, pop);
       break;
     case WGL_GREEN_BITS_ARB:
+      pop = iWGLAttr[++cur];
       PUSH2(oGLXAttr, GLX_GREEN_SIZE, pop);
       TRACE("pAttr[%d] = GLX_GREEN_SIZE: %d\n", cur, pop);
       break;
     case WGL_ALPHA_BITS_ARB:
+      pop = iWGLAttr[++cur];
       PUSH2(oGLXAttr, GLX_ALPHA_SIZE, pop);
       TRACE("pAttr[%d] = GLX_ALPHA_SIZE: %d\n", cur, pop);
       break;
     case WGL_DEPTH_BITS_ARB:
+      pop = iWGLAttr[++cur];
       PUSH2(oGLXAttr, GLX_DEPTH_SIZE, pop);
       TRACE("pAttr[%d] = GLX_DEPTH_SIZE: %d\n", cur, pop);
       break;
     case WGL_STENCIL_BITS_ARB:
+      pop = iWGLAttr[++cur];
       PUSH2(oGLXAttr, GLX_STENCIL_SIZE, pop);
       TRACE("pAttr[%d] = GLX_STENCIL_SIZE: %d\n", cur, pop);
       break;
     case WGL_DOUBLE_BUFFER_ARB:
+      pop = iWGLAttr[++cur];
       PUSH2(oGLXAttr, GLX_DOUBLEBUFFER, pop);
       TRACE("pAttr[%d] = GLX_DOUBLEBUFFER: %d\n", cur, pop);
       break;
     case WGL_STEREO_ARB:
+      pop = iWGLAttr[++cur];
       PUSH2(oGLXAttr, GLX_STEREO, pop);
       TRACE("pAttr[%d] = GLX_STEREO: %d\n", cur, pop);
       break;
 
     case WGL_PIXEL_TYPE_ARB:
+      pop = iWGLAttr[++cur];
       TRACE("pAttr[%d] = WGL_PIXEL_TYPE_ARB: %d\n", cur, pop);
       switch (pop) {
       case WGL_TYPE_COLORINDEX_ARB: pixelattrib = GLX_COLOR_INDEX_BIT; break ;
@@ -818,20 +895,24 @@ static int ConvertAttribWGLtoGLX(const int* iWGLAttr, int* oGLXAttr, struct wgl_
       case WGL_TYPE_RGBA_UNSIGNED_FLOAT_EXT: pixelattrib = GLX_RGBA_UNSIGNED_FLOAT_BIT_EXT; break ;
       default:
         ERR("unexpected PixelType(%x)\n", pop);	
+        pop = 0;
       }
       break;
 
     case WGL_SUPPORT_GDI_ARB:
       /* This flag is set in a pixel format */
+      pop = iWGLAttr[++cur];
       TRACE("pAttr[%d] = WGL_SUPPORT_GDI_ARB: %d\n", cur, pop);
       break;
 
     case WGL_DRAW_TO_BITMAP_ARB:
       /* This flag is set in a pixel format */
+      pop = iWGLAttr[++cur];
       TRACE("pAttr[%d] = WGL_DRAW_TO_BITMAP_ARB: %d\n", cur, pop);
       break;
 
     case WGL_DRAW_TO_WINDOW_ARB:
+      pop = iWGLAttr[++cur];
       TRACE("pAttr[%d] = WGL_DRAW_TO_WINDOW_ARB: %d\n", cur, pop);
       /* GLX_DRAWABLE_TYPE flags need to be OR'd together. See below. */
       if (pop) {
@@ -840,6 +921,7 @@ static int ConvertAttribWGLtoGLX(const int* iWGLAttr, int* oGLXAttr, struct wgl_
       break;
 
     case WGL_DRAW_TO_PBUFFER_ARB:
+      pop = iWGLAttr[++cur];
       TRACE("pAttr[%d] = WGL_DRAW_TO_PBUFFER_ARB: %d\n", cur, pop);
       /* GLX_DRAWABLE_TYPE flags need to be OR'd together. See below. */
       if (pop) {
@@ -849,15 +931,18 @@ static int ConvertAttribWGLtoGLX(const int* iWGLAttr, int* oGLXAttr, struct wgl_
 
     case WGL_ACCELERATION_ARB:
       /* This flag is set in a pixel format */
+      pop = iWGLAttr[++cur];
       TRACE("pAttr[%d] = WGL_ACCELERATION_ARB: %d\n", cur, pop);
       break;
 
     case WGL_SUPPORT_OPENGL_ARB:
+      pop = iWGLAttr[++cur];
       /** nothing to do, if we are here, supposing support Accelerated OpenGL */
       TRACE("pAttr[%d] = WGL_SUPPORT_OPENGL_ARB: %d\n", cur, pop);
       break;
 
     case WGL_SWAP_METHOD_ARB:
+      pop = iWGLAttr[++cur];
       TRACE("pAttr[%d] = WGL_SWAP_METHOD_ARB: %#x\n", cur, pop);
       if (has_swap_method)
       {
@@ -885,16 +970,19 @@ static int ConvertAttribWGLtoGLX(const int* iWGLAttr, int* oGLXAttr, struct wgl_
       break;
 
     case WGL_PBUFFER_LARGEST_ARB:
+      pop = iWGLAttr[++cur];
       PUSH2(oGLXAttr, GLX_LARGEST_PBUFFER, pop);
       TRACE("pAttr[%d] = GLX_LARGEST_PBUFFER: %x\n", cur, pop);
       break;
 
     case WGL_SAMPLE_BUFFERS_ARB:
+      pop = iWGLAttr[++cur];
       PUSH2(oGLXAttr, GLX_SAMPLE_BUFFERS_ARB, pop);
       TRACE("pAttr[%d] = GLX_SAMPLE_BUFFERS_ARB: %x\n", cur, pop);
       break;
 
     case WGL_SAMPLES_ARB:
+      pop = iWGLAttr[++cur];
       PUSH2(oGLXAttr, GLX_SAMPLES_ARB, pop);
       TRACE("pAttr[%d] = GLX_SAMPLES_ARB: %x\n", cur, pop);
       break;
@@ -902,7 +990,8 @@ static int ConvertAttribWGLtoGLX(const int* iWGLAttr, int* oGLXAttr, struct wgl_
     case WGL_TEXTURE_FORMAT_ARB:
     case WGL_TEXTURE_TARGET_ARB:
     case WGL_MIPMAP_TEXTURE_ARB:
-      TRACE("WGL_render_texture Attributes: %x as %x\n", iWGLAttr[cur - 1], iWGLAttr[cur]);
+      TRACE("WGL_render_texture Attributes: %x as %x\n", iWGLAttr[cur], iWGLAttr[cur + 1]);
+      pop = iWGLAttr[++cur];
       if (NULL == pbuf) {
         ERR("trying to use GLX_Pbuffer Attributes without Pbuffer (was %x)\n", iWGLAttr[cur]);
       }
@@ -916,7 +1005,7 @@ static int ConvertAttribWGLtoGLX(const int* iWGLAttr, int* oGLXAttr, struct wgl_
       }
       break ;
     case WGL_FLOAT_COMPONENTS_NV:
-      nvfloatattrib = pop;
+      nvfloatattrib = iWGLAttr[++cur];
       TRACE("pAttr[%d] = WGL_FLOAT_COMPONENTS_NV: %x\n", cur, nvfloatattrib);
       break ;
     case WGL_BIND_TO_TEXTURE_DEPTH_NV:
@@ -926,22 +1015,26 @@ static int ConvertAttribWGLtoGLX(const int* iWGLAttr, int* oGLXAttr, struct wgl_
     case WGL_BIND_TO_TEXTURE_RECTANGLE_FLOAT_RG_NV:
     case WGL_BIND_TO_TEXTURE_RECTANGLE_FLOAT_RGB_NV:
     case WGL_BIND_TO_TEXTURE_RECTANGLE_FLOAT_RGBA_NV:
+      pop = iWGLAttr[++cur];
       /** cannot be converted, see direct handling on 
        *   - wglGetPixelFormatAttribivARB
        *  TODO: wglChoosePixelFormat
        */
       break ;
     case WGL_FRAMEBUFFER_SRGB_CAPABLE_EXT:
+      pop = iWGLAttr[++cur];
       PUSH2(oGLXAttr, GLX_FRAMEBUFFER_SRGB_CAPABLE_EXT, pop);
       TRACE("pAttr[%d] = GLX_FRAMEBUFFER_SRGB_CAPABLE_EXT: %x\n", cur, pop);
       break ;
 
     case WGL_TYPE_RGBA_UNSIGNED_FLOAT_EXT:
+      pop = iWGLAttr[++cur];
       PUSH2(oGLXAttr, GLX_RGBA_UNSIGNED_FLOAT_TYPE_EXT, pop);
       TRACE("pAttr[%d] = GLX_RGBA_UNSIGNED_FLOAT_TYPE_EXT: %x\n", cur, pop);
       break ;
     default:
-      FIXME("unsupported %x WGL Attribute\n", attr);
+      FIXME("unsupported %x WGL Attribute\n", iWGLAttr[cur]);
+      cur++;
       break;
     }
     ++cur;
@@ -1331,10 +1424,17 @@ static struct gl_drawable *create_gl_drawable( HWND hwnd, const struct wgl_pixel
 
     if (!known_child && !GetWindow( hwnd, GW_CHILD ) && GetAncestor( hwnd, GA_PARENT ) == GetDesktopWindow())  /* childless top-level window */
     {
+        struct x11drv_win_data *data;
+
         gl->type = DC_GL_WINDOW;
         gl->window = create_client_window( hwnd, visual );
         if (gl->window)
             gl->drawable = pglXCreateWindow( gdi_display, gl->format->fbconfig, gl->window, NULL );
+        data = get_win_data( hwnd );
+        gl->fs_hack = data->fs_hack;
+        if (gl->fs_hack)
+            TRACE( "Window %p has the fullscreen hack enabled\n", hwnd );
+        release_win_data( data );
         TRACE( "%p created client %lx drawable %lx\n", hwnd, gl->window, gl->drawable );
     }
 #ifdef SONAME_LIBXCOMPOSITE
@@ -1454,6 +1554,9 @@ static BOOL set_pixel_format(HDC hdc, int format, BOOL allow_change)
 void sync_gl_drawable( HWND hwnd, BOOL known_child )
 {
     struct gl_drawable *old, *new;
+    struct x11drv_win_data *data;
+
+    TRACE("%p\n", hwnd);
 
     if (!(old = get_gl_drawable( hwnd, 0 ))) return;
 
@@ -1472,6 +1575,15 @@ void sync_gl_drawable( HWND hwnd, BOOL known_child )
     default:
         break;
     }
+
+    if (DC_GL_PIXMAP_WIN != old->type) {
+        data = get_win_data( hwnd );
+        old->fs_hack = data->fs_hack;
+        if (old->fs_hack)
+            TRACE( "Window %p has the fullscreen hack enabled\n", hwnd );
+        release_win_data( data );
+    }
+
     release_gl_drawable( old );
 }
 
@@ -1768,6 +1880,10 @@ static BOOL glxdrv_wglDeleteContext(struct wgl_context *ctx)
 static PROC glxdrv_wglGetProcAddress(LPCSTR lpszProc)
 {
     if (!strncmp(lpszProc, "wgl", 3)) return NULL;
+    if (!strcmp(lpszProc, "glBindFramebuffer"))
+        return (PROC)wglBindFramebuffer;
+    if (!strcmp(lpszProc, "glBindFramebufferEXT"))
+        return (PROC)wglBindFramebufferEXT;
     return pglXGetProcAddressARB((const GLubyte*)lpszProc);
 }
 
@@ -1787,12 +1903,234 @@ static void set_context_drawables( struct wgl_context *ctx, struct gl_drawable *
     for (i = 0; i < 4; i++) release_gl_drawable( prev[i] );
 }
 
+struct fs_hack_fbconfig_attribs
+{
+    int render_type;
+    int buffer_size;
+    int red_size;
+    int green_size;
+    int blue_size;
+    int alpha_size;
+    int depth_size;
+    int stencil_size;
+    int doublebuffer;
+    int samples;
+    int srgb;
+};
+
+struct fs_hack_fbo_attachments_config
+{
+    GLint color_internalformat;
+    GLenum color_format;
+    GLenum color_type;
+    GLint ds_internalformat;
+    GLenum ds_format;
+    GLenum ds_type;
+    int samples;
+};
+
+static void fs_hack_get_attachments_config( struct gl_drawable *gl, struct fs_hack_fbconfig_attribs *attribs,
+        struct fs_hack_fbo_attachments_config *config )
+{
+    if (attribs->render_type != GLX_RGBA_BIT)
+        FIXME( "Unsupported GLX_RENDER_TYPE %#x.\n", attribs->render_type );
+    if (attribs->red_size != 8 || attribs->green_size != 8 || attribs->blue_size != 8)
+        FIXME( "Unsupported RGBA color sizes {%u, %u, %u, %u}.\n",
+                attribs->red_size, attribs->green_size, attribs->blue_size, attribs->alpha_size );
+    if (attribs->srgb)
+        config->color_internalformat = attribs->alpha_size ? GL_SRGB8_ALPHA8 : GL_SRGB8;
+    else
+        config->color_internalformat = attribs->alpha_size ? GL_RGBA8 : GL_RGB8;
+    config->color_format = GL_BGRA;
+    config->color_type = GL_UNSIGNED_INT_8_8_8_8_REV;
+    if (attribs->depth_size || attribs->stencil_size)
+    {
+        if (attribs->depth_size != 24)
+            FIXME( "Unsupported depth buffer size %u.\n", attribs->depth_size );
+        if (attribs->stencil_size && attribs->stencil_size != 8)
+            FIXME( "Unsupported stencil buffer size %u.\n", attribs->stencil_size );
+        config->ds_internalformat = attribs->stencil_size ? GL_DEPTH24_STENCIL8 : GL_DEPTH_COMPONENT24;
+        config->ds_format = attribs->stencil_size ? GL_DEPTH_STENCIL : GL_DEPTH_COMPONENT;
+        config->ds_type = attribs->stencil_size ? GL_UNSIGNED_INT_24_8 : GL_UNSIGNED_INT;
+    }
+    else
+    {
+        config->ds_internalformat = config->ds_format = config->ds_type = 0;
+    }
+    config->samples = attribs->samples;
+}
+
+static void fs_hack_setup_context( struct wgl_context *ctx, struct gl_drawable *gl )
+{
+    GLuint prev_draw_fbo, prev_read_fbo, prev_texture, prev_renderbuffer;
+    POINT p = fs_hack_current_mode();
+    float prev_clear_color[4];
+    unsigned int i;
+    struct fs_hack_fbo_attachments_config config;
+    struct fs_hack_fbconfig_attribs attribs;
+    static const struct fbconfig_attribs_query
+    {
+        int attribute;
+        unsigned int offset;
+    }
+    queries[] =
+    {
+        {GLX_RENDER_TYPE, offsetof(struct fs_hack_fbconfig_attribs, render_type)},
+        {GLX_BUFFER_SIZE, offsetof(struct fs_hack_fbconfig_attribs, buffer_size)},
+        {GLX_RED_SIZE, offsetof(struct fs_hack_fbconfig_attribs, red_size)},
+        {GLX_GREEN_SIZE, offsetof(struct fs_hack_fbconfig_attribs, green_size)},
+        {GLX_BLUE_SIZE, offsetof(struct fs_hack_fbconfig_attribs, blue_size)},
+        {GLX_ALPHA_SIZE, offsetof(struct fs_hack_fbconfig_attribs, alpha_size)},
+        {GLX_DEPTH_SIZE, offsetof(struct fs_hack_fbconfig_attribs, depth_size)},
+        {GLX_STENCIL_SIZE, offsetof(struct fs_hack_fbconfig_attribs, stencil_size)},
+        {GLX_DOUBLEBUFFER, offsetof(struct fs_hack_fbconfig_attribs, doublebuffer)},
+        {GLX_SAMPLES_ARB, offsetof(struct fs_hack_fbconfig_attribs, samples)},
+        {GLX_FRAMEBUFFER_SRGB_CAPABLE_EXT, offsetof(struct fs_hack_fbconfig_attribs, srgb)},
+    };
+    BYTE *ptr = (BYTE *)&attribs;
+
+    if (ctx->fs_hack)
+    {
+        opengl_funcs.gl.p_glGetIntegerv( GL_DRAW_FRAMEBUFFER_BINDING, (GLint *)&prev_draw_fbo );
+        opengl_funcs.gl.p_glGetIntegerv( GL_READ_FRAMEBUFFER_BINDING, (GLint *)&prev_read_fbo );
+        opengl_funcs.gl.p_glGetIntegerv( GL_TEXTURE_BINDING_2D, (GLint *)&prev_texture );
+        opengl_funcs.gl.p_glGetIntegerv( GL_RENDERBUFFER_BINDING, (GLint *)&prev_renderbuffer );
+        opengl_funcs.gl.p_glGetFloatv( GL_COLOR_CLEAR_VALUE, prev_clear_color );
+        TRACE( "Previous draw FBO %u, read FBO %u for ctx %p\n", prev_draw_fbo, prev_read_fbo, ctx);
+
+        if (!ctx->fs_hack_fbo)
+        {
+            pglGenFramebuffers( 1, &ctx->fs_hack_fbo );
+            pglGenFramebuffers( 1, &ctx->fs_hack_resolve_fbo );
+            TRACE( "Created FBO %u for fullscreen hack.\n", ctx->fs_hack_fbo );
+        }
+        pglBindFramebuffer( GL_DRAW_FRAMEBUFFER, ctx->fs_hack_fbo );
+
+        for (i = 0; i < ARRAY_SIZE(queries); ++i)
+            pglXGetFBConfigAttrib( gdi_display, gl->format->fbconfig, queries[i].attribute,
+                    (int *)&ptr[queries[i].offset] );
+        fs_hack_get_attachments_config( gl, &attribs, &config );
+
+        if (config.samples)
+        {
+            if (!ctx->fs_hack_color_renderbuffer)
+                pglGenRenderbuffers( 1, &ctx->fs_hack_color_renderbuffer );
+            pglBindRenderbuffer( GL_RENDERBUFFER, ctx->fs_hack_color_renderbuffer );
+            pglRenderbufferStorageMultisample( GL_RENDERBUFFER, config.samples,
+                    config.color_internalformat, p.x, p.y );
+            pglFramebufferRenderbuffer( GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+                    GL_RENDERBUFFER, ctx->fs_hack_color_renderbuffer );
+            TRACE( "Created renderbuffer %u for fullscreen hack.\n", ctx->fs_hack_color_renderbuffer );
+            pglGenRenderbuffers( 1, &ctx->fs_hack_color_resolve_renderbuffer );
+            pglBindRenderbuffer( GL_RENDERBUFFER, ctx->fs_hack_color_resolve_renderbuffer );
+            pglRenderbufferStorage( GL_RENDERBUFFER, config.color_internalformat, p.x, p.y );
+            pglBindFramebuffer( GL_DRAW_FRAMEBUFFER, ctx->fs_hack_resolve_fbo );
+            pglFramebufferRenderbuffer( GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+                    GL_RENDERBUFFER, ctx->fs_hack_color_resolve_renderbuffer );
+            pglBindFramebuffer( GL_DRAW_FRAMEBUFFER, ctx->fs_hack_fbo );
+            pglBindRenderbuffer( GL_RENDERBUFFER, prev_renderbuffer );
+            TRACE( "Also created renderbuffer %u and FBO %u for color resolve.\n",
+                    ctx->fs_hack_color_resolve_renderbuffer, ctx->fs_hack_resolve_fbo );
+        }
+        else
+        {
+            if (!ctx->fs_hack_color_texture)
+                opengl_funcs.gl.p_glGenTextures( 1, &ctx->fs_hack_color_texture );
+            opengl_funcs.gl.p_glBindTexture( GL_TEXTURE_2D, ctx->fs_hack_color_texture );
+            opengl_funcs.gl.p_glTexImage2D( GL_TEXTURE_2D, 0, config.color_internalformat, p.x, p.y,
+                    0, config.color_format, config.color_type, NULL);
+            opengl_funcs.gl.p_glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
+            opengl_funcs.gl.p_glBindTexture( GL_TEXTURE_2D, prev_texture );
+            pglFramebufferTexture2D( GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+                    GL_TEXTURE_2D, ctx->fs_hack_color_texture, 0 );
+            TRACE( "Created texture %u for fullscreen hack.\n", ctx->fs_hack_color_texture );
+        }
+
+        if (config.ds_internalformat)
+        {
+            if (config.samples)
+            {
+                if (!ctx->fs_hack_ds_renderbuffer)
+                    pglGenRenderbuffers( 1, &ctx->fs_hack_ds_renderbuffer );
+                pglBindRenderbuffer( GL_RENDERBUFFER, ctx->fs_hack_ds_renderbuffer );
+                pglRenderbufferStorageMultisample( GL_RENDERBUFFER, config.samples,
+                        config.ds_internalformat, p.x, p.y );
+                pglBindRenderbuffer( GL_RENDERBUFFER, prev_renderbuffer );
+                if (attribs.depth_size)
+                    pglFramebufferRenderbuffer( GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
+                            GL_RENDERBUFFER, ctx->fs_hack_ds_renderbuffer );
+                if (attribs.stencil_size)
+                    pglFramebufferRenderbuffer( GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT,
+                            GL_RENDERBUFFER, ctx->fs_hack_ds_renderbuffer );
+                TRACE( "Created DS renderbuffer %u for fullscreen hack.\n", ctx->fs_hack_ds_renderbuffer );
+            }
+            else
+            {
+                if (!ctx->fs_hack_ds_texture)
+                    opengl_funcs.gl.p_glGenTextures( 1, &ctx->fs_hack_ds_texture );
+                opengl_funcs.gl.p_glBindTexture( GL_TEXTURE_2D, ctx->fs_hack_ds_texture );
+                opengl_funcs.gl.p_glTexImage2D( GL_TEXTURE_2D, 0, config.ds_internalformat, p.x, p.y,
+                        0, config.ds_format, config.ds_type, NULL);
+                opengl_funcs.gl.p_glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
+                opengl_funcs.gl.p_glBindTexture( GL_TEXTURE_2D, prev_texture );
+                if (attribs.depth_size)
+                    pglFramebufferTexture2D( GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, ctx->fs_hack_ds_texture, 0 );
+                if (attribs.stencil_size)
+                    pglFramebufferTexture2D( GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, ctx->fs_hack_ds_texture, 0 );
+                TRACE( "Created DS texture %u for fullscreen hack.\n", ctx->fs_hack_ds_texture );
+            }
+        }
+
+        opengl_funcs.gl.p_glClearColor( 0.0f, 0.0f, 0.0f, 1.0f );
+        if(!gl->fs_hack_context_set_up)
+            opengl_funcs.gl.p_glClear( GL_COLOR_BUFFER_BIT );
+        pglBindFramebuffer( GL_DRAW_FRAMEBUFFER, 0 );
+        pglDrawBuffer( GL_BACK );
+        if(!gl->fs_hack_context_set_up)
+            opengl_funcs.gl.p_glClear( GL_COLOR_BUFFER_BIT );
+        opengl_funcs.gl.p_glClearColor( prev_clear_color[0], prev_clear_color[1], prev_clear_color[2], prev_clear_color[3] );
+        wglBindFramebuffer( GL_DRAW_FRAMEBUFFER, prev_draw_fbo );
+        wglBindFramebuffer( GL_READ_FRAMEBUFFER, prev_read_fbo );
+
+        ctx->setup_for = p;
+        gl->has_scissor_indexed = has_extension(glExtensions, "GL_ARB_viewport_array");
+        gl->fs_hack_context_set_up = TRUE;
+    }
+    else
+    {
+        TRACE( "Releasing fullscreen hack texture %u and FBO %u\n", ctx->fs_hack_color_texture, ctx->fs_hack_fbo );
+        if (ctx->current_draw_fbo == ctx->fs_hack_fbo)
+        {
+            pglBindFramebuffer( GL_DRAW_FRAMEBUFFER, 0 );
+            ctx->current_draw_fbo = 0;
+        }
+        if (ctx->current_read_fbo == ctx->fs_hack_fbo)
+        {
+            pglBindFramebuffer( GL_READ_FRAMEBUFFER, 0 );
+            ctx->current_read_fbo = 0;
+        }
+
+        pglDeleteRenderbuffers( 1, &ctx->fs_hack_ds_renderbuffer );
+        pglDeleteRenderbuffers( 1, &ctx->fs_hack_color_resolve_renderbuffer );
+        pglDeleteRenderbuffers( 1, &ctx->fs_hack_color_renderbuffer );
+        opengl_funcs.gl.p_glDeleteTextures( 1, &ctx->fs_hack_ds_texture );
+        opengl_funcs.gl.p_glDeleteTextures( 1, &ctx->fs_hack_color_texture );
+        ctx->fs_hack_color_renderbuffer = ctx->fs_hack_color_resolve_renderbuffer = ctx->fs_hack_ds_renderbuffer = 0;
+        ctx->fs_hack_color_texture = ctx->fs_hack_ds_texture = 0;
+        pglDeleteFramebuffers( 1, &ctx->fs_hack_resolve_fbo );
+        pglDeleteFramebuffers( 1, &ctx->fs_hack_fbo );
+        ctx->fs_hack_fbo = 0;
+
+        gl->fs_hack_context_set_up = FALSE;
+    }
+}
+
 /***********************************************************************
  *		glxdrv_wglMakeCurrent
  */
 static BOOL WINAPI glxdrv_wglMakeCurrent(HDC hdc, struct wgl_context *ctx)
 {
-    BOOL ret = FALSE;
+    BOOL ret = FALSE, setup_fs_hack = FALSE;
     struct gl_drawable *gl;
 
     TRACE("(%p,%p)\n", hdc, ctx);
@@ -1821,10 +2159,17 @@ static BOOL glxdrv_wglMakeCurrent(HDC hdc, struct wgl_context *ctx)
         if (ret)
         {
             NtCurrentTeb()->glContext = ctx;
-            ctx->has_been_current = TRUE;
+            if (ctx->fs_hack != gl->fs_hack || (ctx->fs_hack && ctx->drawables[0] != gl))
+                setup_fs_hack = TRUE;
             ctx->hdc = hdc;
             set_context_drawables( ctx, gl, gl );
             ctx->refresh_drawables = FALSE;
+            if (setup_fs_hack)
+            {
+                ctx->fs_hack = gl->fs_hack;
+                fs_hack_setup_context( ctx, gl );
+            }
+            ctx->has_been_current = TRUE;
             LeaveCriticalSection( &context_section );
             goto done;
         }
@@ -1843,7 +2188,7 @@ done:
  */
 static BOOL X11DRV_wglMakeContextCurrentARB( HDC draw_hdc, HDC read_hdc, struct wgl_context *ctx )
 {
-    BOOL ret = FALSE;
+    BOOL ret = FALSE, setup_fs_hack = FALSE;
     struct gl_drawable *draw_gl, *read_gl = NULL;
 
     TRACE("(%p,%p,%p)\n", draw_hdc, read_hdc, ctx);
@@ -1866,11 +2211,18 @@ static BOOL X11DRV_wglMakeContextCurrentARB( HDC draw_hdc, HDC read_hdc, struct
                                      read_gl ? read_gl->drawable : 0, ctx->ctx);
         if (ret)
         {
-            ctx->has_been_current = TRUE;
+            NtCurrentTeb()->glContext = ctx;
+            if (ctx->fs_hack != draw_gl->fs_hack || (ctx->fs_hack && ctx->drawables[0] != draw_gl))
+                setup_fs_hack = TRUE;
             ctx->hdc = draw_hdc;
             set_context_drawables( ctx, draw_gl, read_gl );
             ctx->refresh_drawables = FALSE;
-            NtCurrentTeb()->glContext = ctx;
+            if (setup_fs_hack)
+            {
+                ctx->fs_hack = draw_gl->fs_hack;
+                fs_hack_setup_context( ctx, draw_gl );
+            }
+            ctx->has_been_current = TRUE;
             LeaveCriticalSection( &context_section );
             goto done;
         }
@@ -1927,12 +2279,135 @@ static BOOL glxdrv_wglShareLists(struct wgl_context *org, struct wgl_context *de
     return FALSE;
 }
 
+static void wglBindFramebuffer( GLenum target, GLuint framebuffer )
+{
+    struct wgl_context *ctx = NtCurrentTeb()->glContext;
+
+    TRACE( "target %#x, framebuffer %u\n", target, framebuffer );
+    if (ctx->fs_hack && !framebuffer)
+        framebuffer = ctx->fs_hack_fbo;
+
+    if (target == GL_DRAW_FRAMEBUFFER || target == GL_FRAMEBUFFER)
+        ctx->current_draw_fbo = framebuffer;
+    if (target == GL_READ_FRAMEBUFFER || target == GL_FRAMEBUFFER)
+        ctx->current_read_fbo = framebuffer;
+
+    pglBindFramebuffer( target, framebuffer );
+}
+
+static void wglBindFramebufferEXT( GLenum target, GLuint framebuffer )
+{
+    struct wgl_context *ctx = NtCurrentTeb()->glContext;
+
+    TRACE( "target %#x, framebuffer %u\n", target, framebuffer );
+    if (ctx->fs_hack && !framebuffer)
+        framebuffer = ctx->fs_hack_fbo;
+
+    if (target == GL_DRAW_FRAMEBUFFER || target == GL_FRAMEBUFFER)
+        ctx->current_draw_fbo = framebuffer;
+    if (target == GL_READ_FRAMEBUFFER || target == GL_FRAMEBUFFER)
+        ctx->current_read_fbo = framebuffer;
+
+    pglBindFramebufferEXT( target, framebuffer );
+}
+
+static void wglDrawBuffer( GLenum buffer )
+{
+    struct wgl_context *ctx = NtCurrentTeb()->glContext;
+
+    if (ctx->fs_hack && ctx->current_draw_fbo == ctx->fs_hack_fbo)
+    {
+        TRACE("Overriding %#x with GL_COLOR_ATTACHMENT0\n", buffer);
+        buffer = GL_COLOR_ATTACHMENT0;
+    }
+    pglDrawBuffer( buffer );
+}
+
+static void wglReadBuffer( GLenum buffer )
+{
+    struct wgl_context *ctx = NtCurrentTeb()->glContext;
+
+    if (ctx->fs_hack && ctx->current_read_fbo == ctx->fs_hack_fbo)
+    {
+        TRACE("Overriding %#x with GL_COLOR_ATTACHMENT0\n", buffer);
+        buffer = GL_COLOR_ATTACHMENT0;
+    }
+    pglReadBuffer( buffer );
+}
+
+static void fs_hack_blit_framebuffer( struct gl_drawable *gl, GLenum draw_buffer )
+{
+    struct wgl_context *ctx = NtCurrentTeb()->glContext;
+    POINT scaled = fs_hack_get_scaled_screen_size();
+    GLuint prev_draw_fbo, prev_read_fbo;
+    GLint prev_scissor[4];
+    POINT src = fs_hack_current_mode();
+    POINT real = fs_hack_real_mode();
+    POINT scaled_origin = {0, 0};
+    float prev_clear_color[4];
+
+    fs_hack_user_to_real(&scaled_origin);
+
+    if(ctx->setup_for.x != src.x ||
+            ctx->setup_for.y != src.y)
+        fs_hack_setup_context( ctx, gl );
+
+    TRACE( "Blitting from FBO %u %ux%u to %ux%u\n", ctx->fs_hack_fbo, src.x, src.y, scaled.x, scaled.y );
+
+    opengl_funcs.gl.p_glGetIntegerv( GL_DRAW_FRAMEBUFFER_BINDING, (GLint *)&prev_draw_fbo );
+    opengl_funcs.gl.p_glGetIntegerv( GL_READ_FRAMEBUFFER_BINDING, (GLint *)&prev_read_fbo );
+    TRACE( "Previous draw FBO %u, read FBO %u\n", prev_draw_fbo, prev_read_fbo );
+
+    if(gl->has_scissor_indexed){
+        opengl_funcs.ext.p_glGetIntegeri_v(GL_SCISSOR_BOX, 0, prev_scissor);
+        opengl_funcs.ext.p_glScissorIndexed(0, 0, 0, real.x, real.y);
+    }else{
+        opengl_funcs.gl.p_glGetIntegerv(GL_SCISSOR_BOX, prev_scissor);
+        opengl_funcs.gl.p_glScissor(0, 0, real.x, real.y);
+    }
+
+    pglBindFramebuffer( GL_READ_FRAMEBUFFER, ctx->fs_hack_fbo );
+    if (ctx->fs_hack_color_resolve_renderbuffer)
+    {
+        pglBindFramebuffer( GL_DRAW_FRAMEBUFFER, ctx->fs_hack_resolve_fbo );
+        pglBlitFramebuffer( 0, 0, src.x, src.y, 0, 0, src.x, src.y, GL_COLOR_BUFFER_BIT, GL_NEAREST );
+        pglBindFramebuffer( GL_READ_FRAMEBUFFER, ctx->fs_hack_resolve_fbo );
+    }
+    pglBindFramebuffer( GL_DRAW_FRAMEBUFFER, 0 );
+
+    //HACK
+    //pglDrawBuffer( draw_buffer );
+    pglDrawBuffer( GL_BACK );
+
+    opengl_funcs.gl.p_glGetFloatv( GL_COLOR_CLEAR_VALUE, prev_clear_color );
+    opengl_funcs.gl.p_glClearColor( 0.0f, 0.0f, 0.0f, 1.0f );
+    opengl_funcs.gl.p_glClear( GL_COLOR_BUFFER_BIT );
+    opengl_funcs.gl.p_glClearColor( prev_clear_color[0], prev_clear_color[1], prev_clear_color[2], prev_clear_color[3] );
+
+    pglBlitFramebuffer( 0, 0, src.x, src.y, scaled_origin.x, scaled_origin.y, scaled_origin.x + scaled.x, scaled_origin.y + scaled.y, GL_COLOR_BUFFER_BIT, GL_LINEAR );
+    //HACK
+    if ( draw_buffer == GL_FRONT )
+        pglXSwapBuffers(gdi_display, gl->drawable);
+
+    if(gl->has_scissor_indexed){
+        opengl_funcs.ext.p_glScissorIndexedv(0, prev_scissor);
+    }else{
+        opengl_funcs.gl.p_glScissor(prev_scissor[0], prev_scissor[1],
+                prev_scissor[2], prev_scissor[3]);
+    }
+
+    pglBindFramebuffer( GL_DRAW_FRAMEBUFFER, prev_draw_fbo );
+    pglBindFramebuffer( GL_READ_FRAMEBUFFER, prev_read_fbo );
+}
+
 static void wglFinish(void)
 {
     struct x11drv_escape_flush_gl_drawable escape;
     struct gl_drawable *gl;
     struct wgl_context *ctx = NtCurrentTeb()->glContext;
 
+    TRACE("\n");
+
     escape.code = X11DRV_FLUSH_GL_DRAWABLE;
     escape.gl_drawable = 0;
     escape.flush = FALSE;
@@ -1946,6 +2421,18 @@ static void wglFinish(void)
         default: break;
         }
         sync_context(ctx);
+
+        if (gl->fs_hack) {
+            ctx->fs_hack = gl->fs_hack;
+            if(!gl->fs_hack_context_set_up)
+                fs_hack_setup_context( ctx, gl );
+            if(!gl->fs_hack_did_swapbuf)
+                fs_hack_blit_framebuffer( gl, GL_FRONT );
+        }else if(gl->fs_hack_context_set_up){
+            ctx->fs_hack = FALSE;
+            fs_hack_setup_context(ctx, gl);
+        }
+
         release_gl_drawable( gl );
     }
 
@@ -1959,6 +2446,8 @@ static void wglFlush(void)
     struct gl_drawable *gl;
     struct wgl_context *ctx = NtCurrentTeb()->glContext;
 
+    TRACE("\n");
+
     escape.code = X11DRV_FLUSH_GL_DRAWABLE;
     escape.gl_drawable = 0;
     escape.flush = FALSE;
@@ -1972,9 +2461,20 @@ static void wglFlush(void)
         default: break;
         }
         sync_context(ctx);
+
+        if (gl->fs_hack) {
+            ctx->fs_hack = gl->fs_hack;
+            if(!gl->fs_hack_context_set_up)
+                fs_hack_setup_context( ctx, gl );
+            if(!gl->fs_hack_did_swapbuf)
+                fs_hack_blit_framebuffer( gl, GL_FRONT );
+        }else if(gl->fs_hack_context_set_up){
+            ctx->fs_hack = FALSE;
+            fs_hack_setup_context(ctx, gl);
+        }
+
         release_gl_drawable( gl );
     }
-
     pglFlush();
     if (escape.gl_drawable) ExtEscape( ctx->hdc, X11DRV_ESCAPE, sizeof(escape), (LPSTR)&escape, 0, NULL );
 }
@@ -3297,6 +3797,16 @@ static BOOL glxdrv_wglSwapBuffers( HDC hdc )
             target_sbc = pglXSwapBuffersMscOML( gdi_display, gl->drawable, 0, 0, 0 );
             break;
         }
+        if (gl->fs_hack){
+            ctx->fs_hack = gl->fs_hack;
+            if(!gl->fs_hack_context_set_up)
+                fs_hack_setup_context( ctx, gl );
+            fs_hack_blit_framebuffer( gl, GL_BACK );
+            gl->fs_hack_did_swapbuf = TRUE;
+        }else if(gl->fs_hack_context_set_up){
+            ctx->fs_hack = FALSE;
+            fs_hack_setup_context(ctx, gl);
+        }
         pglXSwapBuffers(gdi_display, gl->drawable);
         break;
     }
diff --git a/dlls/winex11.drv/settings.c b/dlls/winex11.drv/settings.c
index 781a55699e..1ab0807741 100644
--- a/dlls/winex11.drv/settings.c
+++ b/dlls/winex11.drv/settings.c
@@ -21,7 +21,9 @@
 #include "config.h"
 #include <string.h>
 #include <stdio.h>
+#include <stdlib.h>
 #include <assert.h>
+#include <math.h>
 
 #define NONAMELESSUNION
 #define NONAMELESSSTRUCT
@@ -49,6 +51,36 @@ static int (*pGetCurrentMode)(void);
 static LONG (*pSetCurrentMode)(int mode);
 static const char *handler_name;
 
+static const struct fs_mode {
+    int w, h;
+} fs_modes[] = {
+    /* this table should provide a few resolution options for common display
+     * ratios, so users can choose to render at lower resolution for
+     * performance. */
+    { 640,  480}, /*  4:3 */
+    { 800,  600}, /*  4:3 */
+    {1024,  768}, /*  4:3 */
+    {1600, 1200}, /*  4:3 */
+    { 960,  540}, /* 16:9 */
+    {1280,  720}, /* 16:9 */
+    {1600,  900}, /* 16:9 */
+    {1920, 1080}, /* 16:9 */
+    {2560, 1440}, /* 16:9 */
+    {2880, 1620}, /* 16:9 */
+    {3200, 1800}, /* 16:9 */
+    {3840, 2160}, /* 16:9 */
+    {1440,  900}, /*  8:5 */
+    {1680, 1050}, /*  8:5 */
+    {1920, 1200}, /*  8:5 */
+    {2560, 1600}, /*  8:5 */
+    {1440,  960}, /*  3:2 */
+    {1920, 1280}, /*  3:2 */
+    {2560, 1080}, /* 21:9 ultra-wide */
+    {1920,  800}, /* 12:5 */
+    {3840, 1600}, /* 12:5 */
+    {1280, 1024}, /*  5:4 */
+};
+
 /*
  * Set the handlers for resolution changing functions
  * and initialize the master list of modes
@@ -60,12 +92,14 @@ struct x11drv_mode_info *X11DRV_Settings_SetHandlers(const char *name,
                                                      int reserve_depths)
 {
     handler_name = name;
-    pGetCurrentMode = pNewGCM;
-    pSetCurrentMode = pNewSCM;
+    if(pNewGCM)
+        pGetCurrentMode = pNewGCM;
+    if(pNewSCM)
+        pSetCurrentMode = pNewSCM;
     TRACE("Resolution settings now handled by: %s\n", name);
     if (reserve_depths)
-        /* leave room for other depths */
-        dd_max_modes = (3+1)*(nmodes);
+        /* leave room for other depths and refresh rates */
+        dd_max_modes = 2*(3+1)*(nmodes);
     else 
         dd_max_modes = nmodes;
 
@@ -81,16 +115,29 @@ struct x11drv_mode_info *X11DRV_Settings_SetHandlers(const char *name,
 }
 
 /* Add one mode to the master list */
-void X11DRV_Settings_AddOneMode(unsigned int width, unsigned int height, unsigned int bpp, unsigned int freq)
+BOOL X11DRV_Settings_AddOneMode(unsigned int width, unsigned int height, unsigned int bpp, unsigned int freq)
 {
+    unsigned int i;
     struct x11drv_mode_info *info = &dd_modes[dd_mode_count];
     DWORD dwBpp = screen_bpp;
     if (dd_mode_count >= dd_max_modes)
     {
         ERR("Maximum modes (%d) exceeded\n", dd_max_modes);
-        return;
+        return FALSE;
     }
     if (bpp == 0) bpp = dwBpp;
+
+    for(i = 0; i < dd_mode_count; ++i)
+    {
+        if(dd_modes[i].width == width &&
+                dd_modes[i].height == height &&
+                dd_modes[i].refresh_rate == freq &&
+                dd_modes[i].bpp == bpp)
+        {
+            return FALSE;
+        }
+    }
+
     info->width         = width;
     info->height        = height;
     info->refresh_rate  = freq;
@@ -98,8 +145,32 @@ void X11DRV_Settings_AddOneMode(unsigned int width, unsigned int height, unsigne
     TRACE("initialized mode %d: %dx%dx%d @%d Hz (%s)\n", 
           dd_mode_count, width, height, bpp, freq, handler_name);
     dd_mode_count++;
+
+    return TRUE;
 }
 
+static int sort_display_modes(const void *l, const void *r)
+{
+    const struct x11drv_mode_info *left = l, *right = r;
+
+    /* largest first */
+    if(left->width < right->width)
+        return 1;
+
+    if(left->width > right->width)
+        return -1;
+
+    if(left->height < right->height)
+        return 1;
+
+    if(left->height > right->height)
+        return -1;
+
+    return 0;
+}
+
+static int currentMode = -1, realMode = -1;
+
 /* copy all the current modes using the other color depths */
 void X11DRV_Settings_AddDepthModes(void)
 {
@@ -107,7 +178,34 @@ void X11DRV_Settings_AddDepthModes(void)
     int existing_modes = dd_mode_count;
     DWORD dwBpp = screen_bpp;
     const DWORD *depths = screen_bpp == 32 ? depths_32 : depths_24;
+    struct fs_mode real_mode;
+    unsigned int real_rate;
+
+    real_mode.w = dd_modes[realMode].width;
+    real_mode.h = dd_modes[realMode].height;
+    real_rate = dd_modes[realMode].refresh_rate;
+
+    /* Linux reports far fewer resolutions than Windows; add "missing" modes
+     * that some games may expect. */
+    for(i = 0; i < ARRAY_SIZE(fs_modes); ++i)
+    {
+        if(fs_modes[i].w <= real_mode.w &&
+                fs_modes[i].h <= real_mode.h)
+            X11DRV_Settings_AddOneMode(fs_modes[i].w, fs_modes[i].h, 0, dd_modes[realMode].refresh_rate);
+    }
 
+    qsort(dd_modes, dd_mode_count, sizeof(*dd_modes), sort_display_modes);
+
+    /* synthesize 60 FPS mode if needed */
+    if(real_rate != 60)
+    {
+        for(i = 0; i < existing_modes; ++i)
+        {
+            X11DRV_Settings_AddOneMode(dd_modes[i].width, dd_modes[i].height, dwBpp, 60);
+        }
+    }
+
+    existing_modes = dd_mode_count;
     for (j=0; j<3; j++)
     {
         if (depths[j] != dwBpp)
@@ -119,6 +217,8 @@ void X11DRV_Settings_AddDepthModes(void)
             }
         }
     }
+
+    X11DRV_Settings_SetRealMode(real_mode.w, real_mode.h);
 }
 
 /* return the number of modes that are initialized */
@@ -131,19 +231,253 @@ unsigned int X11DRV_Settings_GetModeCount(void)
  * Default handlers if resolution switching is not enabled
  *
  */
+double fs_hack_user_to_real_w = 1., fs_hack_user_to_real_h = 1.;
+double fs_hack_real_to_user_w = 1., fs_hack_real_to_user_h = 1.;
+static int offs_x = 0, offs_y = 0;
+static int fs_width = 0, fs_height = 0;
+
+void X11DRV_Settings_SetRealMode(unsigned int w, unsigned int h)
+{
+    unsigned int i;
+
+    currentMode = realMode = -1;
+
+    for(i = 0; i < dd_mode_count; ++i)
+    {
+        if(dd_modes[i].width == w &&
+                dd_modes[i].height == h)
+        {
+            currentMode = i;
+            break;
+        }
+    }
+
+    if(currentMode < 0)
+    {
+        FIXME("Couldn't find current mode?! Returning 0...\n");
+        currentMode = 0;
+    }
+
+    realMode = currentMode;
+
+    TRACE("Set realMode to %d\n", realMode);
+}
+
 static int X11DRV_nores_GetCurrentMode(void)
 {
-    return 0;
+    return currentMode;
+}
+
+BOOL fs_hack_enabled(void)
+{
+    return currentMode >= 0 &&
+        currentMode != realMode;
+}
+
+BOOL fs_hack_mapping_required(void)
+{
+    /* steamcompmgr does our mapping for us */
+    return !wm_is_steamcompmgr(NULL) &&
+        currentMode >= 0 &&
+        currentMode != realMode;
+}
+
+BOOL fs_hack_matches_current_mode(int w, int h)
+{
+    return fs_hack_enabled() &&
+        (w == dd_modes[currentMode].width &&
+         h == dd_modes[currentMode].height);
+}
+
+BOOL fs_hack_matches_real_mode(int w, int h)
+{
+    return fs_hack_enabled() &&
+        (w == dd_modes[realMode].width &&
+         h == dd_modes[realMode].height);
+}
+
+BOOL fs_hack_matches_last_mode(int w, int h)
+{
+    return w == fs_width && h == fs_height;
+}
+
+void fs_hack_scale_user_to_real(POINT *pos)
+{
+    if(fs_hack_mapping_required()){
+        TRACE("from %d,%d\n", pos->x, pos->y);
+        pos->x = lround(pos->x * fs_hack_user_to_real_w);
+        pos->y = lround(pos->y * fs_hack_user_to_real_h);
+        TRACE("to %d,%d\n", pos->x, pos->y);
+    }
+}
+
+void fs_hack_scale_real_to_user(POINT *pos)
+{
+    if(fs_hack_mapping_required()){
+        TRACE("from %d,%d\n", pos->x, pos->y);
+        pos->x = lround(pos->x * fs_hack_real_to_user_w);
+        pos->y = lround(pos->y * fs_hack_real_to_user_h);
+        TRACE("to %d,%d\n", pos->x, pos->y);
+    }
+}
+
+POINT fs_hack_get_scaled_screen_size(void)
+{
+    POINT p = { dd_modes[currentMode].width,
+        dd_modes[currentMode].height };
+    fs_hack_scale_user_to_real(&p);
+    return p;
+}
+
+void fs_hack_user_to_real(POINT *pos)
+{
+    if(fs_hack_mapping_required()){
+        TRACE("from %d,%d\n", pos->x, pos->y);
+        fs_hack_scale_user_to_real(pos);
+        pos->x += offs_x;
+        pos->y += offs_y;
+        TRACE("to %d,%d\n", pos->x, pos->y);
+    }
+}
+
+void fs_hack_real_to_user(POINT *pos)
+{
+    if(fs_hack_mapping_required()){
+        TRACE("from %d,%d\n", pos->x, pos->y);
+
+        if(pos->x <= offs_x)
+            pos->x = 0;
+        else
+            pos->x -= offs_x;
+
+        if(pos->y <= offs_y)
+            pos->y = 0;
+        else
+            pos->y -= offs_y;
+
+        if(pos->x >= fs_width)
+            pos->x = fs_width - 1;
+        if(pos->y >= fs_height)
+            pos->y = fs_height - 1;
+
+        fs_hack_scale_real_to_user(pos);
+
+        TRACE("to %d,%d\n", pos->x, pos->y);
+    }
+}
+
+void fs_hack_rect_user_to_real(RECT *rect)
+{
+    fs_hack_user_to_real((POINT *)&rect->left);
+    fs_hack_user_to_real((POINT *)&rect->right);
+}
+
+/* this is for clipping */
+void fs_hack_rgndata_user_to_real(RGNDATA *data)
+{
+    unsigned int i;
+    XRectangle *xrect;
+
+    if (data && fs_hack_mapping_required())
+    {
+        xrect = (XRectangle *)data->Buffer;
+        for (i = 0; i < data->rdh.nCount; i++)
+        {
+            POINT p;
+            p.x = xrect[i].x;
+            p.y = xrect[i].y;
+            fs_hack_user_to_real(&p);
+            xrect[i].x = p.x;
+            xrect[i].y = p.y;
+            xrect[i].width  *= fs_hack_user_to_real_w;
+            xrect[i].height *= fs_hack_user_to_real_h;
+        }
+    }
 }
 
 static LONG X11DRV_nores_SetCurrentMode(int mode)
 {
-    if (mode == 0) return DISP_CHANGE_SUCCESSFUL;
-    TRACE("Ignoring mode change request mode=%d\n", mode);
-    return DISP_CHANGE_FAILED;
+    if (mode >= dd_mode_count)
+       return DISP_CHANGE_FAILED;
+
+    currentMode = mode;
+    TRACE("set current mode to: %ux%u\n",
+            dd_modes[currentMode].width,
+            dd_modes[currentMode].height);
+    if(currentMode == 0){
+        fs_hack_user_to_real_w = 1.;
+        fs_hack_user_to_real_h = 1.;
+        fs_hack_real_to_user_w = 1.;
+        fs_hack_real_to_user_h = 1.;
+        offs_x = offs_y = 0;
+        fs_width = dd_modes[currentMode].width;
+        fs_height = dd_modes[currentMode].height;
+
+        X11DRV_resize_desktop(
+                DisplayWidth(gdi_display, default_visual.screen),
+                DisplayHeight(gdi_display, default_visual.screen));
+    }else{
+        double w = dd_modes[currentMode].width;
+        double h = dd_modes[currentMode].height;
+        if(dd_modes[realMode].width / (double)dd_modes[realMode].height < w / h){ /* real mode is narrower than fake mode */
+            /* scale to fit width */
+            h = dd_modes[realMode].width * (h / w);
+            w = dd_modes[realMode].width;
+            offs_x = 0;
+            offs_y = (dd_modes[realMode].height - h) / 2;
+            fs_width = dd_modes[realMode].width;
+            fs_height = (int)h;
+        }else{
+            /* scale to fit height */
+            w = dd_modes[realMode].height * (w / h);
+            h = dd_modes[realMode].height;
+            offs_x = (dd_modes[realMode].width - w) / 2;
+            offs_y = 0;
+            fs_width = (int)w;
+            fs_height = dd_modes[realMode].height;
+        }
+        fs_hack_user_to_real_w = w / (double)dd_modes[currentMode].width;
+        fs_hack_user_to_real_h = h / (double)dd_modes[currentMode].height;
+        fs_hack_real_to_user_w = dd_modes[currentMode].width / (double)w;
+        fs_hack_real_to_user_h = dd_modes[currentMode].height / (double)h;
+
+        X11DRV_resize_desktop(
+                DisplayWidth(gdi_display, default_visual.screen) - (dd_modes[realMode].width - w),
+                DisplayHeight(gdi_display, default_visual.screen) - (dd_modes[realMode].height - h));
+    }
+
+    return DISP_CHANGE_SUCCESSFUL;
+}
+
+void fs_hack_choose_mode(int w, int h)
+{
+    unsigned int i;
+
+    for(i = 0; i < dd_mode_count; ++i)
+    {
+        if(dd_modes[i].width == w &&
+                dd_modes[i].height == h)
+        {
+            X11DRV_nores_SetCurrentMode(i);
+            break;
+        }
+    }
+}
+
+POINT fs_hack_current_mode(void)
+{
+    POINT ret = { dd_modes[currentMode].width,
+        dd_modes[currentMode].height };
+    return ret;
+}
+
+POINT fs_hack_real_mode(void)
+{
+    POINT ret = { dd_modes[realMode].width,
+        dd_modes[realMode].height };
+    return ret;
 }
 
-/* default handler only gets the current X desktop resolution */
 void X11DRV_Settings_Init(void)
 {
     RECT primary = get_host_primary_monitor_rect();
diff --git a/dlls/winex11.drv/vulkan.c b/dlls/winex11.drv/vulkan.c
index b0bba86435..9eaeb49049 100644
--- a/dlls/winex11.drv/vulkan.c
+++ b/dlls/winex11.drv/vulkan.c
@@ -596,6 +596,39 @@ static VkResult X11DRV_vkQueuePresentKHR(VkQueue queue, const VkPresentInfoKHR *
     return res;
 }
 
+static VkBool32 X11DRV_query_fs_hack(VkExtent2D *real_sz, VkExtent2D *user_sz,
+        VkRect2D *dst_blit)
+{
+    if(fs_hack_enabled()){
+        POINT real_res = fs_hack_real_mode();
+        POINT user_res = fs_hack_current_mode();
+        POINT scaled = fs_hack_get_scaled_screen_size();
+        POINT scaled_origin = {0, 0};
+
+        fs_hack_user_to_real(&scaled_origin);
+
+        if(real_sz){
+            real_sz->width = real_res.x;
+            real_sz->height = real_res.y;
+        }
+
+        if(user_sz){
+            user_sz->width = user_res.x;
+            user_sz->height = user_res.y;
+        }
+
+        if(dst_blit){
+            dst_blit->offset.x = scaled_origin.x;
+            dst_blit->offset.y = scaled_origin.y;
+            dst_blit->extent.width = scaled.x;
+            dst_blit->extent.height = scaled.y;
+        }
+
+        return VK_TRUE;
+    }
+    return VK_FALSE;
+}
+
 static const struct vulkan_funcs vulkan_funcs =
 {
     X11DRV_vkCreateInstance,
@@ -616,6 +649,7 @@ static const struct vulkan_funcs vulkan_funcs =
     X11DRV_vkGetPhysicalDeviceWin32PresentationSupportKHR,
     X11DRV_vkGetSwapchainImagesKHR,
     X11DRV_vkQueuePresentKHR,
+    X11DRV_query_fs_hack,
 };
 
 static void *X11DRV_get_vk_device_proc_addr(const char *name)
diff --git a/dlls/winex11.drv/window.c b/dlls/winex11.drv/window.c
index 46a3a8a14b..4ae09b8444 100644
--- a/dlls/winex11.drv/window.c
+++ b/dlls/winex11.drv/window.c
@@ -46,7 +46,6 @@
 #include "x11drv.h"
 #include "wine/debug.h"
 #include "wine/server.h"
-#include "mwm.h"
 
 WINE_DEFAULT_DEBUG_CHANNEL(x11drv);
 
@@ -103,6 +102,77 @@ static CRITICAL_SECTION_DEBUG critsect_debug =
 };
 static CRITICAL_SECTION win_data_section = { &critsect_debug, -1, 0, 0, 0, 0 };
 
+static const int WM_UNKNOWN = 0;
+static const int WM_MUTTER = 1;
+static const int WM_STEAMCOMPMGR = 2;
+
+/* enable workarounds for mutter bugs */
+static int detect_wm(Display *dpy)
+{
+    Display *display = dpy ? dpy : thread_init_display(); /* DefaultRootWindow is a macro... */
+    Window root = DefaultRootWindow(display), *wm_check;
+    Atom type;
+    int format;
+    unsigned long count, remaining;
+    char *wm_name;
+
+    static int cached = -1;
+
+    if(cached < 0){
+
+        if (XGetWindowProperty( display, root, x11drv_atom(_NET_SUPPORTING_WM_CHECK), 0,
+                                 sizeof(*wm_check)/sizeof(CARD32), False, x11drv_atom(WINDOW),
+                                 &type, &format, &count, &remaining, (unsigned char **)&wm_check ) == Success){
+            if (type == x11drv_atom(WINDOW)){
+                if(XGetWindowProperty( display, *wm_check, x11drv_atom(_NET_WM_NAME), 0,
+                            256/sizeof(CARD32), False, x11drv_atom(UTF8_STRING),
+                            &type, &format, &count, &remaining, (unsigned char **)&wm_name) == Success &&
+                        type == x11drv_atom(UTF8_STRING)){
+                    /* noop */
+                }else if(XGetWindowProperty( display, *wm_check, x11drv_atom(WM_NAME), 0,
+                            256/sizeof(CARD32), False, x11drv_atom(STRING),
+                            &type, &format, &count, &remaining, (unsigned char **)&wm_name) == Success &&
+                        type == x11drv_atom(STRING)){
+                    /* noop */
+                }else
+                    wm_name = NULL;
+
+                if(wm_name){
+                    TRACE("Got WM name %s\n", wm_name);
+
+                    if((strcmp(wm_name, "GNOME Shell") == 0) ||
+                            (strcmp(wm_name, "Mutter") == 0))
+                        cached = WM_MUTTER;
+                    else if(strcmp(wm_name, "steamcompmgr") == 0)
+                        cached = WM_STEAMCOMPMGR;
+                    else
+                        cached = WM_UNKNOWN;
+
+                    XFree(wm_name);
+                }else{
+                    TRACE("WM did not set _NET_WM_NAME or WM_NAME\n");
+                    cached = WM_UNKNOWN;
+                }
+            }else
+                cached = WM_UNKNOWN;
+
+            XFree(wm_check);
+        }else
+            cached = WM_UNKNOWN;
+    }
+
+    return cached;
+}
+
+BOOL wm_is_mutter(Display *display)
+{
+    return detect_wm(display) == WM_MUTTER;
+}
+
+BOOL wm_is_steamcompmgr(Display *display)
+{
+    return detect_wm(display) == WM_STEAMCOMPMGR;
+}
 
 /***********************************************************************
  * http://standards.freedesktop.org/startup-notification-spec
@@ -319,7 +389,10 @@ static unsigned long get_mwm_decorations( struct x11drv_win_data *data,
         if (style & WS_MAXIMIZEBOX) ret |= MWM_DECOR_MAXIMIZE;
     }
     if (ex_style & WS_EX_DLGMODALFRAME) ret |= MWM_DECOR_BORDER;
-    else if (style & WS_THICKFRAME) ret |= MWM_DECOR_BORDER | MWM_DECOR_RESIZEH;
+    else if (style & WS_THICKFRAME){
+        if((style & WS_CAPTION) == WS_CAPTION)
+             ret |= MWM_DECOR_BORDER | MWM_DECOR_RESIZEH;
+    }
     else if ((style & (WS_DLGFRAME|WS_BORDER)) == WS_DLGFRAME) ret |= MWM_DECOR_BORDER;
     return ret;
 }
@@ -377,6 +450,11 @@ static void sync_window_region( struct x11drv_win_data *data, HRGN win_region )
     HRGN hrgn = win_region;
 
     if (!data->whole_window) return;
+
+    if(data->fs_hack){
+        ERR("shaped windows with fs hack not supported, things may go badly\n");
+    }
+
     data->shaped = FALSE;
 
     if (IsRectEmpty( &data->window_rect ))  /* set an empty shape */
@@ -712,6 +790,13 @@ static void set_size_hints( struct x11drv_win_data *data, DWORD style )
     XFree( size_hints );
 }
 
+static Bool is_unmap_notify( Display *display, XEvent *event, XPointer arg )
+{
+    struct x11drv_win_data *data = (struct x11drv_win_data *)arg;
+    return event->xany.serial >= data->unmapnotify_serial &&
+           event->xany.window == data->whole_window &&
+           event->type == UnmapNotify;
+}
 
 /***********************************************************************
  *              set_mwm_hints
@@ -719,6 +804,34 @@ static void set_size_hints( struct x11drv_win_data *data, DWORD style )
 static void set_mwm_hints( struct x11drv_win_data *data, DWORD style, DWORD ex_style )
 {
     MwmHints mwm_hints;
+    int enable_mutter_workaround, mapped;
+
+    /* workaround for mutter gitlab bug #676, changing decorations of a
+     * fullscreen and unredirected window freezes the compositing.
+     * The window style will be updated again once the window has returned
+     * from fullscreen.
+     */
+    if (wm_is_mutter(data->display) && (data->net_wm_state & (1 << NET_WM_STATE_FULLSCREEN)))
+    {
+        Atom type;
+        int format;
+        unsigned long *property, net_wm_bypass_compositor = 0, count, remaining;
+
+        if (XGetWindowProperty( data->display, data->whole_window, x11drv_atom(_NET_WM_BYPASS_COMPOSITOR), 0,
+                                1, False, XA_CARDINAL, &type, &format, &count, &remaining,
+                                (unsigned char **)&property ) == Success &&
+            property)
+        {
+            net_wm_bypass_compositor = *property;
+            XFree(property);
+        }
+
+        if (net_wm_bypass_compositor)
+        {
+            TRACE("workaround mutter bug, ignoring decorations while compositor is bypassed\n");
+            return;
+        }
+    }
 
     if (data->hwnd == GetDesktopWindow())
     {
@@ -735,19 +848,63 @@ static void set_mwm_hints( struct x11drv_win_data *data, DWORD style, DWORD ex_s
         {
             if (style & WS_MINIMIZEBOX) mwm_hints.functions |= MWM_FUNC_MINIMIZE;
             if (style & WS_MAXIMIZEBOX) mwm_hints.functions |= MWM_FUNC_MAXIMIZE;
-            if (style & WS_SYSMENU)     mwm_hints.functions |= MWM_FUNC_CLOSE;
+            /*if (style & WS_SYSMENU)*/     mwm_hints.functions |= MWM_FUNC_CLOSE;
         }
     }
 
+    mwm_hints.flags = MWM_HINTS_FUNCTIONS | MWM_HINTS_DECORATIONS;
+
+    if (data->prev_hints.flags == mwm_hints.flags &&
+        data->prev_hints.decorations == mwm_hints.decorations &&
+        data->prev_hints.functions == mwm_hints.functions)
+        return;
+
     TRACE( "%p setting mwm hints to %lx,%lx (style %x exstyle %x)\n",
            data->hwnd, mwm_hints.decorations, mwm_hints.functions, style, ex_style );
 
-    mwm_hints.flags = MWM_HINTS_FUNCTIONS | MWM_HINTS_DECORATIONS;
+    enable_mutter_workaround = wm_is_mutter(data->display) && GetFocus() == data->hwnd &&
+                               !!data->prev_hints.decorations != !!mwm_hints.decorations &&
+                               root_window == DefaultRootWindow(data->display);
+
+    /* workaround for mutter gitlab bug #649, we cannot trust the
+     * data->mapped flag as mapping is asynchronous.
+     */
+    if (enable_mutter_workaround)
+    {
+        XWindowAttributes attr;
+
+        mapped = data->mapped;
+        if (XGetWindowAttributes( data->display, data->whole_window, &attr ))
+            mapped = (attr.map_state != IsUnmapped);
+    }
+
     mwm_hints.input_mode = 0;
     mwm_hints.status = 0;
+    data->unmapnotify_serial = NextRequest( data->display );
     XChangeProperty( data->display, data->whole_window, x11drv_atom(_MOTIF_WM_HINTS),
                      x11drv_atom(_MOTIF_WM_HINTS), 32, PropModeReplace,
                      (unsigned char*)&mwm_hints, sizeof(mwm_hints)/sizeof(long) );
+
+    if (enable_mutter_workaround)
+    {
+        XEvent event;
+
+        /* workaround for mutter gitlab bug #649, wait for the map notify
+         * event each time the decorations are modified before modifying
+         * them again.
+         */
+        if (mapped)
+        {
+            TRACE("workaround mutter bug #649, waiting for UnmapNotify\n");
+            XPeekIfEvent( data->display, &event, is_unmap_notify, (XPointer)data );
+        }
+
+        /* workaround for mutter gitlab bug #273 */
+        TRACE("workaround mutter bug, setting take_focus_back\n");
+        data->take_focus_back = GetTickCount64();
+    }
+
+    data->prev_hints = mwm_hints;
 }
 
 
@@ -884,7 +1041,7 @@ static void make_owner_managed( HWND hwnd )
  *
  * Set all the window manager hints for a window.
  */
-static void set_wm_hints( struct x11drv_win_data *data )
+void set_wm_hints( struct x11drv_win_data *data )
 {
     DWORD style, ex_style;
 
@@ -961,21 +1118,28 @@ void update_net_wm_states( struct x11drv_win_data *data )
     style = GetWindowLongW( data->hwnd, GWL_STYLE );
     if (style & WS_MINIMIZE)
         new_state |= data->net_wm_state & ((1 << NET_WM_STATE_FULLSCREEN)|(1 << NET_WM_STATE_MAXIMIZED));
-    if (is_window_rect_fullscreen( &data->whole_rect ))
+    if ((!data->fs_hack || fs_hack_enabled()) && is_window_rect_fullscreen( &data->whole_rect ))
     {
         if ((style & WS_MAXIMIZE) && (style & WS_CAPTION) == WS_CAPTION)
             new_state |= (1 << NET_WM_STATE_MAXIMIZED);
         else if (!(style & WS_MINIMIZE))
 	{
             net_wm_bypass_compositor = 1;
-            new_state |= (1 << NET_WM_STATE_FULLSCREEN);
+            if (!wm_is_steamcompmgr(data->display) || !fs_hack_enabled())
+                /* when fs hack is enabled, we don't want steamcompmgr to resize the window to be fullscreened */
+                new_state |= (1 << NET_WM_STATE_FULLSCREEN);
 	}
     }
     else if (style & WS_MAXIMIZE)
         new_state |= (1 << NET_WM_STATE_MAXIMIZED);
 
     ex_style = GetWindowLongW( data->hwnd, GWL_EXSTYLE );
-    if (ex_style & WS_EX_TOPMOST)
+    if ((ex_style & WS_EX_TOPMOST) &&
+            /* mutter < 3.31 has a bug where a FULLSCREEN and ABOVE window when
+             * minimized will incorrectly show a black window.  this workaround
+             * should be removed when the fix is widely distributed.  see
+             * mutter issue #306. */
+            !(wm_is_mutter(data->display) && (new_state & (1 << NET_WM_STATE_FULLSCREEN))))
         new_state |= (1 << NET_WM_STATE_ABOVE);
     if (ex_style & (WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE))
         new_state |= (1 << NET_WM_STATE_SKIP_TASKBAR) | (1 << NET_WM_STATE_SKIP_PAGER);
@@ -1021,6 +1185,12 @@ void update_net_wm_states( struct x11drv_win_data *data )
                    i, data->hwnd, data->whole_window,
                    (new_state & (1 << i)) != 0, (data->net_wm_state & (1 << i)) != 0 );
 
+            if(i == NET_WM_STATE_FULLSCREEN)
+            {
+                data->pending_fullscreen = (new_state & (1 << i)) != 0;
+                TRACE("set pending_fullscreen to: %u\n", data->pending_fullscreen);
+            }
+
             xev.xclient.data.l[0] = (new_state & (1 << i)) ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
             xev.xclient.data.l[1] = X11DRV_Atoms[net_wm_state_atoms[i] - FIRST_XATOM];
             xev.xclient.data.l[2] = ((net_wm_state_atoms[i] == XATOM__NET_WM_STATE_MAXIMIZED_VERT) ?
@@ -1031,6 +1201,9 @@ void update_net_wm_states( struct x11drv_win_data *data )
     }
     data->net_wm_state = new_state;
 
+    if(new_state & (1 << NET_WM_STATE_FULLSCREEN))
+        XSetInputFocus( data->display, data->whole_window, RevertToParent, CurrentTime );
+
     XChangeProperty( data->display, data->whole_window, x11drv_atom(_NET_WM_BYPASS_COMPOSITOR), XA_CARDINAL,
                      32, PropModeReplace, (unsigned char *)&net_wm_bypass_compositor, 1 );
 
@@ -1152,6 +1325,7 @@ static void unmap_window( HWND hwnd )
 
         data->mapped = FALSE;
         data->net_wm_state = 0;
+        data->pending_fullscreen = FALSE;
     }
     release_win_data( data );
 }
@@ -1168,6 +1342,7 @@ void make_window_embedded( struct x11drv_win_data *data )
         if (!data->managed) XUnmapWindow( data->display, data->whole_window );
         else XWithdrawWindow( data->display, data->whole_window, data->vis.screen );
         data->net_wm_state = 0;
+        data->pending_fullscreen = FALSE;
     }
     data->embedded = TRUE;
     data->managed = TRUE;
@@ -1249,8 +1424,14 @@ static void sync_window_position( struct x11drv_win_data *data,
     /* resizing a managed maximized window is not allowed */
     if (!(style & WS_MAXIMIZE) || !data->managed)
     {
-        changes.width = data->whole_rect.right - data->whole_rect.left;
-        changes.height = data->whole_rect.bottom - data->whole_rect.top;
+        if(data->fs_hack){
+            POINT p = fs_hack_real_mode();
+            changes.width = p.x;
+            changes.height = p.y;
+        }else{
+            changes.width = data->whole_rect.right - data->whole_rect.left;
+            changes.height = data->whole_rect.bottom - data->whole_rect.top;
+        }
         /* if window rect is empty force size to 1x1 */
         if (changes.width <= 0 || changes.height <= 0) changes.width = changes.height = 1;
         if (changes.width > 65535) changes.width = 65535;
@@ -1332,6 +1513,15 @@ static void sync_client_position( struct x11drv_win_data *data,
     if (changes.width  != old_client_rect->right - old_client_rect->left) mask |= CWWidth;
     if (changes.height != old_client_rect->bottom - old_client_rect->top) mask |= CWHeight;
 
+    if(data->fs_hack){
+        POINT p = fs_hack_real_mode();
+        changes.x = 0;
+        changes.y = 0;
+        changes.width = p.x;
+        changes.height = p.y;
+        mask = CWX | CWY | CWWidth | CWHeight;
+    }
+
     if (mask)
     {
         TRACE( "setting client win %lx pos %d,%d,%dx%d changes=%x\n",
@@ -1485,6 +1675,14 @@ Window create_client_window( HWND hwnd, const XVisualInfo *visual )
     cx = min( max( 1, data->client_rect.right - data->client_rect.left ), 65535 );
     cy = min( max( 1, data->client_rect.bottom - data->client_rect.top ), 65535 );
 
+
+    if(data->fs_hack){
+        POINT p = fs_hack_real_mode();
+        cx = p.x;
+        cy = p.y;
+    }
+
+    TRACE("setting client rect: %u, %u x %ux%u\n", x, y, cx, cy);
     ret = data->client_window = XCreateWindow( gdi_display,
                                                data->whole_window ? data->whole_window : dummy_parent,
                                                x, y, cx, cy, 0, default_visual.depth, InputOutput,
@@ -1536,12 +1734,20 @@ static void create_whole_window( struct x11drv_win_data *data )
         data->whole_colormap = XCreateColormap( data->display, root_window, data->vis.visual, AllocNone );
 
     mask = get_window_attributes( data, &attr );
+    attr.background_pixel = XBlackPixel(data->display, data->vis.screen);
+    mask |= CWBackPixel;
 
     if (!(cx = data->whole_rect.right - data->whole_rect.left)) cx = 1;
     else if (cx > 65535) cx = 65535;
     if (!(cy = data->whole_rect.bottom - data->whole_rect.top)) cy = 1;
     else if (cy > 65535) cy = 65535;
 
+    if(data->fs_hack){
+        POINT p = fs_hack_real_mode();
+        cx = p.x;
+        cy = p.y;
+    }
+
     pos = virtual_screen_to_root( data->whole_rect.left, data->whole_rect.top );
     data->whole_window = XCreateWindow( data->display, root_window, pos.x, pos.y,
                                         cx, cy, 0, data->vis.depth, InputOutput,
@@ -1615,6 +1821,7 @@ static void destroy_whole_window( struct x11drv_win_data *data, BOOL already_des
     data->whole_colormap = 0;
     data->wm_state = WithdrawnState;
     data->net_wm_state = 0;
+    data->pending_fullscreen = FALSE;
     data->mapped = FALSE;
     if (data->xic)
     {
@@ -2277,6 +2484,26 @@ static inline BOOL get_surface_rect( const RECT *visible_rect, RECT *surface_rec
 }
 
 
+BOOL fs_hack_window_is_hacked(HWND hwnd, struct x11drv_win_data *data)
+{
+    BOOL release = FALSE, ret;
+
+    if(!data){
+        data = get_win_data(hwnd);
+        if(!data)
+            return FALSE;
+        release = TRUE;
+    }
+
+    ret = data->fs_hack;
+
+    if(release)
+        release_win_data(data);
+
+    return ret;
+}
+
+
 /***********************************************************************
  *		WindowPosChanging   (X11DRV.@)
  */
@@ -2292,6 +2519,38 @@ void CDECL X11DRV_WindowPosChanging( HWND hwnd, HWND insert_after, UINT swp_flag
 
     if (!data && !(data = X11DRV_create_win_data( hwnd, window_rect, client_rect ))) return;
 
+    if(!wm_is_steamcompmgr(data->display) &&
+            !data->fs_hack &&
+            fs_hack_matches_current_mode(
+                window_rect->right - window_rect->left,
+                window_rect->bottom - window_rect->top)){
+        POINT tl = virtual_screen_to_root(0, 0);
+        POINT p = fs_hack_real_mode();
+        TRACE("Enabling fs hack, resizing the window to (%u,%u)-(%u,%u)\n", tl.x, tl.y, p.x, p.y);
+        data->fs_hack = TRUE;
+        if(data->whole_window)
+            XMoveResizeWindow(data->display, data->whole_window, tl.x, tl.y, p.x, p.y);
+        if(data->client_window)
+            XMoveResizeWindow(data->display, data->client_window, 0, 0, p.x, p.y);
+    }else if(data->fs_hack &&
+            !fs_hack_matches_current_mode(
+                window_rect->right - window_rect->left,
+                window_rect->bottom - window_rect->top)){
+        TRACE("Disabling fs hack\n");
+        data->fs_hack = FALSE;
+        if(data->whole_window)
+            XMoveResizeWindow(data->display, data->whole_window,
+                    window_rect->left, window_rect->top,
+                    window_rect->right - window_rect->left,
+                    window_rect->bottom - window_rect->top);
+        if(data->client_window){
+            XMoveResizeWindow(data->display, data->client_window,
+                    data->client_rect.left, data->client_rect.top,
+                    data->client_rect.right - data->client_rect.left,
+                    data->client_rect.bottom - data->client_rect.top);
+        }
+    }
+
     /* check if we need to switch the window to managed */
     if (!data->managed && data->whole_window && is_window_managed( hwnd, swp_flags, window_rect ))
     {
@@ -2426,6 +2685,9 @@ void CDECL X11DRV_WindowPosChanged( HWND hwnd, HWND insert_after, UINT swp_flags
         return;
     }
 
+    if (data->fs_hack)
+        sync_gl_drawable( hwnd, FALSE );
+
     /* check if we are currently processing an event relevant to this window */
     event_type = 0;
     if (thread_data &&
@@ -2453,7 +2715,7 @@ void CDECL X11DRV_WindowPosChanged( HWND hwnd, HWND insert_after, UINT swp_flags
     }
 
     /* don't change position if we are about to minimize or maximize a managed window */
-    if (!event_type &&
+    if ((!event_type || event_type == PropertyNotify) &&
         !(data->managed && (swp_flags & SWP_STATECHANGED) && (new_style & (WS_MINIMIZE|WS_MAXIMIZE))))
         sync_window_position( data, swp_flags, &old_window_rect, &old_whole_rect, &old_client_rect );
 
@@ -2487,7 +2749,7 @@ void CDECL X11DRV_WindowPosChanged( HWND hwnd, HWND insert_after, UINT swp_flags
         else
         {
             if (swp_flags & (SWP_FRAMECHANGED|SWP_STATECHANGED)) set_wm_hints( data );
-            if (!event_type) update_net_wm_states( data );
+            if (!event_type || event_type == PropertyNotify) update_net_wm_states( data );
         }
     }
 
@@ -2551,7 +2813,16 @@ UINT CDECL X11DRV_ShowWindow( HWND hwnd, INT cmd, RECT *rect, UINT swp )
                   &root, &x, &y, &width, &height, &border, &depth );
     XTranslateCoordinates( thread_data->display, data->whole_window, root, 0, 0, &x, &y, &top );
     pos = root_to_virtual_screen( x, y );
-    X11DRV_X_to_window_rect( data, rect, pos.x, pos.y, width, height );
+    if(data->fs_hack){
+        POINT p = fs_hack_current_mode();
+        rect->left = 0;
+        rect->top = 0;
+        rect->right = p.x;
+        rect->bottom = p.y;
+        X11DRV_X_to_window_rect( data, rect, 0, 0, p.x, p.y );
+    }else{
+        X11DRV_X_to_window_rect( data, rect, pos.x, pos.y, width, height );
+    }
     swp &= ~(SWP_NOMOVE | SWP_NOCLIENTMOVE | SWP_NOSIZE | SWP_NOCLIENTSIZE);
 
 done:
@@ -2759,7 +3030,8 @@ LRESULT CDECL X11DRV_WindowMessage( HWND hwnd, UINT msg, WPARAM wp, LPARAM lp )
         }
         return 0;
     case WM_X11DRV_RESIZE_DESKTOP:
-        X11DRV_resize_desktop( LOWORD(lp), HIWORD(lp) );
+        fs_hack_choose_mode(LOWORD(wp), HIWORD(wp));
+        //X11DRV_resize_desktop( LOWORD(lp), HIWORD(lp) );
         return 0;
     case WM_X11DRV_SET_CURSOR:
         if ((data = get_win_data( hwnd )))
diff --git a/dlls/winex11.drv/x11drv.h b/dlls/winex11.drv/x11drv.h
index 1f6a37a7a5..e526cb300f 100644
--- a/dlls/winex11.drv/x11drv.h
+++ b/dlls/winex11.drv/x11drv.h
@@ -59,6 +59,8 @@ typedef int Status;
 #include "wine/gdi_driver.h"
 #include "wine/list.h"
 
+#include "mwm.h"
+
 #define MAX_DASHLEN 16
 
 #define WINE_XDND_VERSION 5
@@ -311,6 +313,7 @@ struct x11drv_escape_flush_gl_drawable
     enum x11drv_escape_codes code;         /* escape code (X11DRV_FLUSH_GL_DRAWABLE) */
     Drawable                 gl_drawable;  /* GL drawable */
     BOOL                     flush;        /* flush X11 before copying */
+    BOOL                     fs_hack;
 };
 
 /**************************************************************************
@@ -322,6 +325,7 @@ struct x11drv_valuator_data
     double min;
     double max;
     int number;
+    double accum;
 };
 
 struct x11drv_thread_data
@@ -426,6 +430,7 @@ enum x11drv_atoms
     XATOM_TEXT,
     XATOM_TIMESTAMP,
     XATOM_UTF8_STRING,
+    XATOM_STRING,
     XATOM_RAW_ASCENT,
     XATOM_RAW_DESCENT,
     XATOM_RAW_CAP_HEIGHT,
@@ -433,6 +438,7 @@ enum x11drv_atoms
     XATOM_Rel_Y,
     XATOM_WM_PROTOCOLS,
     XATOM_WM_DELETE_WINDOW,
+    XATOM_WM_NAME,
     XATOM_WM_STATE,
     XATOM_WM_TAKE_FOCUS,
     XATOM_DndProtocol,
@@ -442,6 +448,7 @@ enum x11drv_atoms
     XATOM__NET_STARTUP_INFO_BEGIN,
     XATOM__NET_STARTUP_INFO,
     XATOM__NET_SUPPORTED,
+    XATOM__NET_SUPPORTING_WM_CHECK,
     XATOM__NET_SYSTEM_TRAY_OPCODE,
     XATOM__NET_SYSTEM_TRAY_S0,
     XATOM__NET_SYSTEM_TRAY_VISUAL,
@@ -492,6 +499,7 @@ enum x11drv_atoms
     XATOM_WCF_SYLK,
     XATOM_WCF_TIFF,
     XATOM_WCF_WAVE,
+    XATOM_WINDOW,
     XATOM_image_bmp,
     XATOM_image_gif,
     XATOM_image_jpeg,
@@ -575,15 +583,20 @@ struct x11drv_win_data
     BOOL        shaped : 1;     /* is window using a custom region shape? */
     BOOL        layered : 1;    /* is window layered and with valid attributes? */
     BOOL        use_alpha : 1;  /* does window use an alpha channel? */
+    BOOL        fs_hack : 1;
+    BOOL        pending_fullscreen : 1;
+    ULONGLONG   take_focus_back;
     int         wm_state;       /* current value of the WM_STATE property */
     DWORD       net_wm_state;   /* bit mask of active x11drv_net_wm_state values */
     Window      embedder;       /* window id of embedder */
+    unsigned long unmapnotify_serial; /* serial number of last UnmapNotify event */
     unsigned long configure_serial; /* serial number of last configure request */
     struct window_surface *surface;
     Pixmap         icon_pixmap;
     Pixmap         icon_mask;
     unsigned long *icon_bits;
     unsigned int   icon_size;
+    MwmHints prev_hints;
 };
 
 extern struct x11drv_win_data *get_win_data( HWND hwnd ) DECLSPEC_HIDDEN;
@@ -608,6 +621,29 @@ extern void change_systray_owner( Display *display, Window systray_window ) DECL
 extern void update_systray_balloon_position(void) DECLSPEC_HIDDEN;
 extern HWND create_foreign_window( Display *display, Window window ) DECLSPEC_HIDDEN;
 extern BOOL update_clipboard( HWND hwnd ) DECLSPEC_HIDDEN;
+extern BOOL wm_is_mutter(Display *) DECLSPEC_HIDDEN;
+extern BOOL wm_is_steamcompmgr(Display *) DECLSPEC_HIDDEN;
+
+extern void set_wm_hints( struct x11drv_win_data *data ) DECLSPEC_HIDDEN;
+extern BOOL fs_hack_enabled(void) DECLSPEC_HIDDEN;
+extern BOOL fs_hack_mapping_required(void) DECLSPEC_HIDDEN;
+extern BOOL fs_hack_matches_current_mode(int w, int h) DECLSPEC_HIDDEN;
+extern BOOL fs_hack_matches_real_mode(int w, int h) DECLSPEC_HIDDEN;
+extern POINT fs_hack_current_mode(void) DECLSPEC_HIDDEN;
+extern POINT fs_hack_real_mode(void) DECLSPEC_HIDDEN;
+extern void fs_hack_user_to_real(POINT *pos) DECLSPEC_HIDDEN;
+extern void fs_hack_real_to_user(POINT *pos) DECLSPEC_HIDDEN;
+extern void fs_hack_scale_user_to_real(POINT *pos) DECLSPEC_HIDDEN;
+extern void fs_hack_scale_real_to_user(POINT *pos) DECLSPEC_HIDDEN;
+extern void fs_hack_rect_user_to_real(RECT *data) DECLSPEC_HIDDEN;
+extern void fs_hack_rgndata_user_to_real(RGNDATA *data) DECLSPEC_HIDDEN;
+extern POINT fs_hack_get_scaled_screen_size(void) DECLSPEC_HIDDEN;
+extern BOOL fs_hack_window_is_hacked(HWND hwnd, struct x11drv_win_data *data) DECLSPEC_HIDDEN;
+extern void fs_hack_xrender_copy(Drawable src, Drawable dst) DECLSPEC_HIDDEN;
+extern double fs_hack_user_to_real_w, fs_hack_user_to_real_h DECLSPEC_HIDDEN;
+extern double fs_hack_real_to_user_w, fs_hack_real_to_user_h DECLSPEC_HIDDEN;
+BOOL fs_hack_matches_last_mode(int w, int h) DECLSPEC_HIDDEN;
+void fs_hack_choose_mode(int w, int h) DECLSPEC_HIDDEN;
 
 static inline void mirror_rect( const RECT *window_rect, RECT *rect )
 {
@@ -665,7 +701,7 @@ extern BOOL is_virtual_desktop(void) DECLSPEC_HIDDEN;
 extern BOOL is_desktop_fullscreen(void) DECLSPEC_HIDDEN;
 extern BOOL create_desktop_win_data( Window win ) DECLSPEC_HIDDEN;
 extern void X11DRV_Settings_AddDepthModes(void) DECLSPEC_HIDDEN;
-extern void X11DRV_Settings_AddOneMode(unsigned int width, unsigned int height, unsigned int bpp, unsigned int freq) DECLSPEC_HIDDEN;
+extern BOOL X11DRV_Settings_AddOneMode(unsigned int width, unsigned int height, unsigned int bpp, unsigned int freq) DECLSPEC_HIDDEN;
 unsigned int X11DRV_Settings_GetModeCount(void) DECLSPEC_HIDDEN;
 void X11DRV_Settings_Init(void) DECLSPEC_HIDDEN;
 struct x11drv_mode_info *X11DRV_Settings_SetHandlers(const char *name,
@@ -673,6 +709,7 @@ struct x11drv_mode_info *X11DRV_Settings_SetHandlers(const char *name,
                                                      LONG (*pNewSCM)(int),
                                                      unsigned int nmodes,
                                                      int reserve_depths) DECLSPEC_HIDDEN;
+void X11DRV_Settings_SetRealMode(unsigned int w, unsigned int h) DECLSPEC_HIDDEN;
 
 void X11DRV_XF86VM_Init(void) DECLSPEC_HIDDEN;
 void X11DRV_XRandR_Init(void) DECLSPEC_HIDDEN;
diff --git a/dlls/winex11.drv/x11drv_main.c b/dlls/winex11.drv/x11drv_main.c
index 968fff121c..c994b2ca1a 100644
--- a/dlls/winex11.drv/x11drv_main.c
+++ b/dlls/winex11.drv/x11drv_main.c
@@ -67,16 +67,16 @@ Colormap default_colormap = None;
 XPixmapFormatValues **pixmap_formats;
 unsigned int screen_bpp;
 Window root_window;
-BOOL usexvidmode = TRUE;
+BOOL usexvidmode = FALSE;
 BOOL usexrandr = TRUE;
 BOOL usexcomposite = TRUE;
 BOOL use_xkb = TRUE;
-BOOL use_take_focus = TRUE;
+BOOL use_take_focus = FALSE;
 BOOL use_primary_selection = FALSE;
 BOOL use_system_cursors = TRUE;
 BOOL show_systray = TRUE;
 BOOL grab_pointer = TRUE;
-BOOL grab_fullscreen = FALSE;
+BOOL grab_fullscreen = TRUE;
 BOOL managed_mode = TRUE;
 BOOL decorated_mode = TRUE;
 BOOL private_color_map = FALSE;
@@ -139,6 +139,7 @@ static const char * const atom_names[NB_XATOMS - FIRST_XATOM] =
     "TEXT",
     "TIMESTAMP",
     "UTF8_STRING",
+    "STRING",
     "RAW_ASCENT",
     "RAW_DESCENT",
     "RAW_CAP_HEIGHT",
@@ -146,6 +147,7 @@ static const char * const atom_names[NB_XATOMS - FIRST_XATOM] =
     "Rel Y",
     "WM_PROTOCOLS",
     "WM_DELETE_WINDOW",
+    "WM_NAME",
     "WM_STATE",
     "WM_TAKE_FOCUS",
     "DndProtocol",
@@ -155,6 +157,7 @@ static const char * const atom_names[NB_XATOMS - FIRST_XATOM] =
     "_NET_STARTUP_INFO_BEGIN",
     "_NET_STARTUP_INFO",
     "_NET_SUPPORTED",
+    "_NET_SUPPORTING_WM_CHECK",
     "_NET_SYSTEM_TRAY_OPCODE",
     "_NET_SYSTEM_TRAY_S0",
     "_NET_SYSTEM_TRAY_VISUAL",
@@ -205,6 +208,7 @@ static const char * const atom_names[NB_XATOMS - FIRST_XATOM] =
     "WCF_SYLK",
     "WCF_TIFF",
     "WCF_WAVE",
+    "WINDOW",
     "image/bmp",
     "image/gif",
     "image/jpeg",
@@ -306,6 +310,9 @@ static int error_handler( Display *display, XErrorEvent *error_evt )
              error_evt->serial, error_evt->request_code );
         DebugBreak();  /* force an entry in the debugger */
     }
+    TRACE("passing on error %d req %d:%d res 0x%lx\n",
+            error_evt->error_code, error_evt->request_code,
+            error_evt->minor_code, error_evt->resourceid);
     old_error_handler( display, error_evt );
     return 0;
 }
diff --git a/dlls/winex11.drv/xinerama.c b/dlls/winex11.drv/xinerama.c
index 4903f52b23..e6e34013f2 100644
--- a/dlls/winex11.drv/xinerama.c
+++ b/dlls/winex11.drv/xinerama.c
@@ -129,6 +129,15 @@ static int query_screens(void)
         }
 
         get_primary()->dwFlags |= MONITORINFOF_PRIMARY;
+
+        if(fs_hack_enabled()){
+            POINT fs = fs_hack_current_mode();
+            MONITORINFOEXW *primary = get_primary();
+            primary->rcMonitor.right = primary->rcMonitor.left + fs.x;
+            primary->rcMonitor.bottom = primary->rcMonitor.top + fs.y;
+            primary->rcWork = primary->rcMonitor;
+        }
+
     }
     else count = 0;
 
diff --git a/dlls/winex11.drv/xrandr.c b/dlls/winex11.drv/xrandr.c
index 930e0282be..2d17ff4d4f 100644
--- a/dlls/winex11.drv/xrandr.c
+++ b/dlls/winex11.drv/xrandr.c
@@ -223,6 +223,9 @@ static void xrandr10_init_modes(void)
     int sizes_count;
     int i, j, nmodes = 0;
 
+    ERR("xrandr 1.2 support required\n");
+    return;
+
     sizes = pXRRSizes( gdi_display, DefaultScreen(gdi_display), &sizes_count );
     if (sizes_count <= 0) return;
 
@@ -480,10 +483,12 @@ static XRRCrtcInfo *xrandr12_get_primary_crtc_info( XRRScreenResources *resource
 
 static int xrandr12_init_modes(void)
 {
-    unsigned int only_one_resolution = 1, mode_count;
+    unsigned int only_one_resolution = 1, mode_count, primary_width, primary_height;
     XRRScreenResources *resources;
     XRROutputInfo *output_info;
+    XRRModeInfo *primary_mode = NULL;
     XRRCrtcInfo *crtc_info;
+    unsigned int primary_refresh, primary_dots;
     int ret = -1;
     int i, j;
 
@@ -497,6 +502,14 @@ static int xrandr12_init_modes(void)
         return ret;
     }
 
+    for (i = 0; i < resources->nmode; ++i)
+    {
+        if (resources->modes[i].id == crtc_info->mode)
+        {
+            primary_mode = &resources->modes[i];
+        }
+    }
+
     TRACE("CRTC %d: mode %#lx, %ux%u+%d+%d.\n", primary_crtc, crtc_info->mode,
           crtc_info->width, crtc_info->height, crtc_info->x, crtc_info->y);
 
@@ -523,10 +536,34 @@ static int xrandr12_init_modes(void)
     }
 
     dd_modes = X11DRV_Settings_SetHandlers( "XRandR 1.2",
-                                            xrandr12_get_current_mode,
-                                            xrandr12_set_current_mode,
+                                            NULL,
+                                            NULL,
                                             output_info->nmode, 1 );
 
+    if(primary_mode)
+    {
+        primary_dots = primary_mode->hTotal * primary_mode->vTotal;
+        primary_refresh = primary_dots ? (primary_mode->dotClock + primary_dots / 2) / primary_dots : 0;
+        primary_width = primary_mode->width;
+        primary_height = primary_mode->height;
+
+    }
+    else
+    {
+        WARN("Couldn't find primary mode! defaulting to 60 Hz\n");
+        primary_refresh = 60;
+        primary_width = crtc_info->width;
+        primary_height = crtc_info->height;
+    }
+
+    if((crtc_info->rotation & RR_Rotate_90) ||
+            (crtc_info->rotation & RR_Rotate_270))
+    {
+        unsigned int tmp = primary_width;
+        primary_width = primary_height;
+        primary_height = tmp;
+    }
+
     xrandr_mode_count = 0;
     for (i = 0; i < output_info->nmode; ++i)
     {
@@ -536,11 +573,22 @@ static int xrandr12_init_modes(void)
 
             if (mode->id == output_info->modes[i])
             {
-                unsigned int refresh = get_frequency( mode );
+                XRRModeInfo rotated_mode = *mode;
+                if((crtc_info->rotation & RR_Rotate_90) ||
+                        (crtc_info->rotation & RR_Rotate_270))
+                {
+                    unsigned int tmp = rotated_mode.width;
+                    rotated_mode.width = rotated_mode.height;
+                    rotated_mode.height = tmp;
+                }
 
-                TRACE("Adding mode %#lx: %ux%u@%u.\n", mode->id, mode->width, mode->height, refresh);
-                X11DRV_Settings_AddOneMode( mode->width, mode->height, 0, refresh );
-                xrandr12_modes[xrandr_mode_count++] = mode->id;
+                if(rotated_mode.width <= primary_width &&
+                        rotated_mode.height <= primary_height &&
+                        X11DRV_Settings_AddOneMode( rotated_mode.width, rotated_mode.height, 0, primary_refresh ))
+                {
+                    TRACE("Added mode %#lx: %ux%u@%u.\n", rotated_mode.id, rotated_mode.width, rotated_mode.height, primary_refresh);
+                    xrandr12_modes[xrandr_mode_count++] = rotated_mode.id;
+                }
                 break;
             }
         }
@@ -557,21 +604,7 @@ static int xrandr12_init_modes(void)
         }
     }
 
-    /* Recent (304.64, possibly earlier) versions of the nvidia driver only
-     * report a DFP's native mode through RandR 1.2 / 1.3. Standard DMT modes
-     * are only listed through RandR 1.0 / 1.1. This is completely useless,
-     * but NVIDIA considers this a feature, so it's unlikely to change. The
-     * best we can do is to fall back to RandR 1.0 and encourage users to
-     * consider more cooperative driver vendors when we detect such a
-     * configuration. */
-    if (only_one_resolution && XQueryExtension( gdi_display, "NV-CONTROL", &i, &j, &ret ))
-    {
-        ERR_(winediag)("Broken NVIDIA RandR detected, falling back to RandR 1.0. "
-                       "Please consider using the Nouveau driver instead.\n");
-        ret = -1;
-        HeapFree( GetProcessHeap(), 0, xrandr12_modes );
-        goto done;
-    }
+    X11DRV_Settings_SetRealMode(primary_width, primary_height);
 
     X11DRV_Settings_AddDepthModes();
     ret = 0;
@@ -924,7 +957,7 @@ static BOOL xrandr14_get_monitors( ULONG_PTR adapter_id, struct x11drv_monitor *
     XRRScreenResources *screen_resources = NULL;
     XRROutputInfo *output_info = NULL, *enum_output_info = NULL;
     XRRCrtcInfo *crtc_info = NULL, *enum_crtc_info;
-    INT primary_index = 0, monitor_count = 0, capacity;
+    INT primary_index = -1, monitor_count = 0, capacity;
     RECT work_rect, primary_rect;
     BOOL ret = FALSE;
     INT i;
@@ -1023,7 +1056,7 @@ static BOOL xrandr14_get_monitors( ULONG_PTR adapter_id, struct x11drv_monitor *
         }
 
         /* Make sure the first monitor is the primary */
-        if (primary_index)
+        if (primary_index > 0)
         {
             struct x11drv_monitor tmp = monitors[0];
             monitors[0] = monitors[primary_index];
@@ -1036,6 +1069,29 @@ static BOOL xrandr14_get_monitors( ULONG_PTR adapter_id, struct x11drv_monitor *
             OffsetRect( &monitors[i].rc_monitor, -primary_rect.left, -primary_rect.top );
             OffsetRect( &monitors[i].rc_work, -primary_rect.left, -primary_rect.top );
         }
+
+        if (primary_index >= 0 && fs_hack_enabled())
+        {
+            /* apply fs hack to primary monitor */
+            POINT fs_hack = fs_hack_current_mode();
+
+            monitors[0].rc_monitor.right = monitors[0].rc_monitor.left + fs_hack.x;
+            monitors[0].rc_monitor.bottom = monitors[0].rc_monitor.top + fs_hack.y;
+
+            fs_hack.x = monitors[0].rc_work.left;
+            fs_hack.y = monitors[0].rc_work.top;
+            fs_hack_real_to_user(&fs_hack);
+            monitors[0].rc_work.left = fs_hack.x;
+            monitors[0].rc_work.top = fs_hack.y;
+
+            fs_hack.x = monitors[0].rc_work.right;
+            fs_hack.y = monitors[0].rc_work.bottom;
+            fs_hack_real_to_user(&fs_hack);
+            monitors[0].rc_work.right = fs_hack.x;
+            monitors[0].rc_work.bottom = fs_hack.y;
+
+            /* TODO adjust other monitor positions */
+        }
     }
 
     *new_monitors = monitors;
diff --git a/dlls/winex11.drv/xrender.c b/dlls/winex11.drv/xrender.c
index 6fe1990233..8d5e59a923 100644
--- a/dlls/winex11.drv/xrender.c
+++ b/dlls/winex11.drv/xrender.c
@@ -475,6 +475,7 @@ static void update_xrender_clipping( struct xrender_physdev *dev, HRGN rgn )
     }
     else if ((data = X11DRV_GetRegionData( rgn, 0 )))
     {
+        fs_hack_rgndata_user_to_real(data);
         pXRenderSetPictureClipRectangles( gdi_display, dev->pict,
                                           dev->x11dev->dc_rect.left, dev->x11dev->dc_rect.top,
                                           (XRectangle *)data->Buffer, data->rdh.nCount );
@@ -1461,6 +1462,46 @@ static void multiply_alpha( Picture pict, XRenderPictFormat *format, int alpha,
     XFreePixmap( gdi_display, mask_pixmap );
 }
 
+/* if we are letterboxing, draw black bars */
+static void fs_hack_draw_black_bars( Picture dst_pict )
+{
+    static const XRenderColor black = { 0, 0, 0, 0xffff };
+    POINT tl, br;   /* top-left / bottom-right */
+    POINT real_mode = fs_hack_real_mode();
+    POINT size = fs_hack_get_scaled_screen_size();
+    XRenderPictureAttributes pa;
+
+    /* first unclip the picture, so that we can actually draw them */
+    pa.clip_mask = None;
+    pXRenderChangePicture( gdi_display, dst_pict, CPClipMask, &pa );
+
+    tl.x = tl.y = 0;
+    fs_hack_user_to_real(&tl);
+    br.x = tl.x + size.x;
+    br.y = tl.y + size.y;
+
+    if (tl.x > 0)
+    {
+        /* black bars left & right */
+        pXRenderFillRectangle(gdi_display, PictOpSrc, dst_pict, &black,
+                0, 0, /* x, y */
+                tl.x, real_mode.y);    /* w, h */
+        pXRenderFillRectangle(gdi_display, PictOpSrc, dst_pict, &black,
+                br.x, 0,
+                real_mode.x - br.x, real_mode.y);
+    }
+    else if (tl.y > 0)
+    {
+        /* black bars top & bottom */
+        pXRenderFillRectangle(gdi_display, PictOpSrc, dst_pict, &black,
+                0, 0,
+                real_mode.x, tl.y);
+        pXRenderFillRectangle(gdi_display, PictOpSrc, dst_pict, &black,
+                0, br.y,
+                real_mode.x, real_mode.y - br.y);
+    }
+}
+
 /* Helper function for (stretched) blitting using xrender */
 static void xrender_blit( int op, Picture src_pict, Picture mask_pict, Picture dst_pict,
                           int x_src, int y_src, int width_src, int height_src,
@@ -1469,6 +1510,20 @@ static void xrender_blit( int op, Picture src_pict, Picture mask_pict, Picture d
 {
     int x_offset, y_offset;
 
+    if (fs_hack_mapping_required())
+    {
+        POINT p;
+        p.x = x_dst;
+        p.y = y_dst;
+        fs_hack_user_to_real(&p);
+        x_dst = p.x;
+        y_dst = p.y;
+        width_dst *= fs_hack_user_to_real_w;
+        height_dst *= fs_hack_user_to_real_h;
+        xscale /= fs_hack_user_to_real_w;
+        yscale /= fs_hack_user_to_real_h;
+    }
+
     if (width_src < 0)
     {
         x_src += width_src + 1;
@@ -1508,6 +1563,9 @@ static void xrender_blit( int op, Picture src_pict, Picture mask_pict, Picture d
     }
     pXRenderComposite( gdi_display, op, src_pict, mask_pict, dst_pict,
                        x_offset, y_offset, 0, 0, x_dst, y_dst, width_dst, height_dst );
+
+    if (fs_hack_mapping_required())
+        fs_hack_draw_black_bars( dst_pict );
 }
 
 /* Helper function for (stretched) mono->color blitting using xrender */
@@ -1688,6 +1746,7 @@ static void xrender_put_image( Pixmap src_pixmap, Picture src_pict, Picture mask
         RGNDATA *clip_data = NULL;
 
         if (clip) clip_data = X11DRV_GetRegionData( clip, 0 );
+        fs_hack_rgndata_user_to_real(clip_data);
         x_dst = dst->x;
         y_dst = dst->y;
         dst_pict = pXRenderCreatePicture( gdi_display, drawable, dst_format, 0, NULL );
@@ -1727,6 +1786,10 @@ static BOOL CDECL xrenderdrv_StretchBlt( PHYSDEV dst_dev, struct bitblt_coords *
     struct xrender_physdev *physdev_src = get_xrender_dev( src_dev );
     BOOL stretch = (src->width != dst->width) || (src->height != dst->height);
 
+    TRACE("src %d,%d %dx%d vis=%s  dst %d,%d %dx%d vis=%s  rop=%06x\n",
+          src->x, src->y, src->width, src->height, wine_dbgstr_rect(&src->visrect),
+          dst->x, dst->y, dst->width, dst->height, wine_dbgstr_rect(&dst->visrect), rop );
+
     if (src_dev->funcs != dst_dev->funcs)
     {
         dst_dev = GET_NEXT_PHYSDEV( dst_dev, pStretchBlt );
@@ -1737,6 +1800,9 @@ static BOOL CDECL xrenderdrv_StretchBlt( PHYSDEV dst_dev, struct bitblt_coords *
     if (physdev_dst->format == WXR_FORMAT_MONO && physdev_src->format != WXR_FORMAT_MONO)
         goto x11drv_fallback;
 
+    if (fs_hack_mapping_required())
+        stretch = TRUE;
+
     /* if not stretching, we only need to handle format conversion */
     if (!stretch && physdev_dst->format == physdev_src->format) goto x11drv_fallback;
 
@@ -1755,8 +1821,17 @@ static BOOL CDECL xrenderdrv_StretchBlt( PHYSDEV dst_dev, struct bitblt_coords *
         tmpGC = XCreateGC( gdi_display, physdev_dst->x11dev->drawable, 0, NULL );
         XSetSubwindowMode( gdi_display, tmpGC, IncludeInferiors );
         XSetGraphicsExposures( gdi_display, tmpGC, False );
-        tmp_pixmap = XCreatePixmap( gdi_display, root_window, tmp.visrect.right - tmp.visrect.left,
-                                    tmp.visrect.bottom - tmp.visrect.top, physdev_dst->pict_format->depth );
+
+        if (fs_hack_mapping_required())
+        {
+            unsigned int real_width  = (tmp.visrect.right - tmp.visrect.left) * fs_hack_user_to_real_w;
+            unsigned int real_height = (tmp.visrect.bottom - tmp.visrect.top) * fs_hack_user_to_real_h;
+            tmp_pixmap = XCreatePixmap( gdi_display, root_window, real_width,
+                                        real_height, physdev_dst->pict_format->depth );
+        }
+        else
+            tmp_pixmap = XCreatePixmap( gdi_display, root_window, tmp.visrect.right - tmp.visrect.left,
+                                        tmp.visrect.bottom - tmp.visrect.top, physdev_dst->pict_format->depth );
 
         xrender_stretch_blit( physdev_src, physdev_dst, tmp_pixmap, src, &tmp );
         execute_rop( physdev_dst->x11dev, tmp_pixmap, tmpGC, &dst->visrect, rop );
@@ -1791,6 +1866,10 @@ static DWORD CDECL xrenderdrv_PutImage( PHYSDEV dev, HRGN clip, BITMAPINFO *info
     Picture src_pict, mask_pict = 0;
     BOOL use_repeat;
 
+    TRACE("src %d,%d %dx%d vis=%s  dst %d,%d %dx%d vis=%s  rop=%06x\n",
+          src->x, src->y, src->width, src->height, wine_dbgstr_rect(&src->visrect),
+          dst->x, dst->y, dst->width, dst->height, wine_dbgstr_rect(&dst->visrect), rop );
+
     dst_format = physdev->format;
     src_format = get_xrender_format_from_bitmapinfo( info );
     if (!(pict_format = pict_formats[src_format])) goto update_format;
@@ -1825,10 +1904,22 @@ static DWORD CDECL xrenderdrv_PutImage( PHYSDEV dev, HRGN clip, BITMAPINFO *info
             gc = XCreateGC( gdi_display, physdev->x11dev->drawable, 0, NULL );
             XSetSubwindowMode( gdi_display, gc, IncludeInferiors );
             XSetGraphicsExposures( gdi_display, gc, False );
-            tmp_pixmap = XCreatePixmap( gdi_display, root_window,
-                                        tmp.visrect.right - tmp.visrect.left,
-                                        tmp.visrect.bottom - tmp.visrect.top,
-                                        physdev->pict_format->depth );
+
+            if (fs_hack_mapping_required())
+            {
+                unsigned int real_width  = (tmp.visrect.right - tmp.visrect.left) * fs_hack_user_to_real_w;
+                unsigned int real_height = (tmp.visrect.bottom - tmp.visrect.top) * fs_hack_user_to_real_h;
+                tmp_pixmap = XCreatePixmap( gdi_display, root_window,
+                                            real_width, real_height,
+                                            physdev->pict_format->depth );
+            }
+            else
+            {
+                tmp_pixmap = XCreatePixmap( gdi_display, root_window,
+                                            tmp.visrect.right - tmp.visrect.left,
+                                            tmp.visrect.bottom - tmp.visrect.top,
+                                            physdev->pict_format->depth );
+            }
 
             xrender_put_image( src_pixmap, src_pict, mask_pict, NULL, physdev->pict_format,
                                NULL, tmp_pixmap, src, &tmp, use_repeat );
diff --git a/include/wine/vulkan_driver.h b/include/wine/vulkan_driver.h
index 02f504e9ae..40ff8c35db 100644
--- a/include/wine/vulkan_driver.h
+++ b/include/wine/vulkan_driver.h
@@ -65,6 +65,13 @@ struct vulkan_funcs
     VkBool32 (*p_vkGetPhysicalDeviceWin32PresentationSupportKHR)(VkPhysicalDevice, uint32_t);
     VkResult (*p_vkGetSwapchainImagesKHR)(VkDevice, VkSwapchainKHR, uint32_t *, VkImage *);
     VkResult (*p_vkQueuePresentKHR)(VkQueue, const VkPresentInfoKHR *);
+
+    /* Optional. Returns TRUE if FS hack is active, otherwise returns FALSE. If
+     * it returns TRUE, then real_sz will contain the actual display
+     * resolution; user_sz will contain the app's requested mode; and dst_blit
+     * will contain the area to blit the user image to in real coordinates.
+     * All parameters are optional. */
+    VkBool32 (*query_fs_hack)(VkExtent2D *real_sz, VkExtent2D *user_sz, VkRect2D *dst_blit);
 };
 
 extern const struct vulkan_funcs * CDECL __wine_get_vulkan_driver(HDC hdc, UINT version);
diff --git a/programs/winecfg/x11drvdlg.c b/programs/winecfg/x11drvdlg.c
index fbc6716e94..1ca8c3ed0f 100644
--- a/programs/winecfg/x11drvdlg.c
+++ b/programs/winecfg/x11drvdlg.c
@@ -123,7 +123,7 @@ static void init_dialog(HWND dialog)
     SendDlgItemMessageW(dialog, IDC_DESKTOP_WIDTH, EM_LIMITTEXT, RES_MAXLEN, 0);
     SendDlgItemMessageW(dialog, IDC_DESKTOP_HEIGHT, EM_LIMITTEXT, RES_MAXLEN, 0);
 
-    buf = get_reg_key(config_key, keypath("X11 Driver"), "GrabFullscreen", "N");
+    buf = get_reg_key(config_key, keypath("X11 Driver"), "GrabFullscreen", "Y");
     if (IS_OPTION_TRUE(*buf))
 	CheckDlgButton(dialog, IDC_FULLSCREEN_GRAB, BST_CHECKED);
     else
From a1e5640b60439f0df83fc24c8a69629cef2c6c67 Mon Sep 17 00:00:00 2001
From: Andrew Eikum <aeikum@codeweavers.com>
Date: Mon, 17 Feb 2020 13:23:52 -0600
Subject: [PATCH] Revert "winex11.drv: Don't request window decoration changes
 if not necessary."

This reverts commit 1e56e5deed3d854da72f415c16c2594898e1a166.

This breaks the ESO launcher.
---
 dlls/winex11.drv/window.c | 8 +-------
 1 file changed, 1 insertion(+), 7 deletions(-)

diff --git a/dlls/winex11.drv/window.c b/dlls/winex11.drv/window.c
index 1f1a2d59e2e..b2700164a3d 100644
--- a/dlls/winex11.drv/window.c
+++ b/dlls/winex11.drv/window.c
@@ -836,13 +836,6 @@ static void set_mwm_hints( struct x11drv_win_data *data, DWORD style, DWORD ex_s
         }
     }
 
-    mwm_hints.flags = MWM_HINTS_FUNCTIONS | MWM_HINTS_DECORATIONS;
-
-    if (data->prev_hints.flags == mwm_hints.flags &&
-        data->prev_hints.decorations == mwm_hints.decorations &&
-        data->prev_hints.functions == mwm_hints.functions)
-        return;
-
     TRACE( "%p setting mwm hints to %lx,%lx (style %x exstyle %x)\n",
            data->hwnd, mwm_hints.decorations, mwm_hints.functions, style, ex_style );
 
@@ -862,6 +855,7 @@ static void set_mwm_hints( struct x11drv_win_data *data, DWORD style, DWORD ex_s
             mapped = (attr.map_state != IsUnmapped);
     }
 
+    mwm_hints.flags = MWM_HINTS_FUNCTIONS | MWM_HINTS_DECORATIONS;
     mwm_hints.input_mode = 0;
     mwm_hints.status = 0;
     data->unmapnotify_serial = NextRequest( data->display );
From 50916cd3dbac0b388a920ba01c673cdc354c7bce Mon Sep 17 00:00:00 2001
From: Georg Lehmann <dadschoorse@gmail.com>
Date: Tue, 25 Feb 2020 13:15:42 +0100
Subject: [PATCH] winevulkan: Create only one fshack compute pipeline per
 swapchain

There is no need to create one pipeline per swapchain image
---
 dlls/winevulkan/vulkan.c         | 46 ++++++++++++++++----------------
 dlls/winevulkan/vulkan_private.h |  2 +-
 2 files changed, 24 insertions(+), 24 deletions(-)

diff --git a/dlls/winevulkan/vulkan.c b/dlls/winevulkan/vulkan.c
index 63665fb7a6f..8667dad9db4 100644
--- a/dlls/winevulkan/vulkan.c
+++ b/dlls/winevulkan/vulkan.c
@@ -1428,7 +1428,7 @@ const uint32_t blit_comp_spv[] = {
 	0x000100fd,0x00010038
 };
 
-static VkResult create_pipeline(VkDevice device, struct VkSwapchainKHR_T *swapchain, struct fs_hack_image *hack, VkShaderModule shaderModule)
+static VkResult create_pipeline(VkDevice device, struct VkSwapchainKHR_T *swapchain, VkShaderModule shaderModule)
 {
     VkResult res;
 #if defined(USE_STRUCT_CONVERSION)
@@ -1446,7 +1446,7 @@ static VkResult create_pipeline(VkDevice device, struct VkSwapchainKHR_T *swapch
     pipelineInfo.basePipelineHandle = VK_NULL_HANDLE;
     pipelineInfo.basePipelineIndex = -1;
 
-    res = device->funcs.p_vkCreateComputePipelines(device->device, VK_NULL_HANDLE, 1, &pipelineInfo, NULL, &hack->pipeline);
+    res = device->funcs.p_vkCreateComputePipelines(device->device, VK_NULL_HANDLE, 1, &pipelineInfo, NULL, &swapchain->pipeline);
     if(res != VK_SUCCESS){
         ERR("vkCreateComputePipelines: %d\n", res);
         return res;
@@ -1509,7 +1509,6 @@ static VkResult create_descriptor_set(VkDevice device, struct VkSwapchainKHR_T *
 
 static void destroy_fs_hack_image(VkDevice device, struct VkSwapchainKHR_T *swapchain, struct fs_hack_image *hack)
 {
-    device->funcs.p_vkDestroyPipeline(device->device, hack->pipeline, NULL);
     device->funcs.p_vkFreeDescriptorSets(device->device, swapchain->descriptor_pool, 1, &hack->descriptor_set);
     device->funcs.p_vkDestroyImageView(device->device, hack->user_view, NULL);
     device->funcs.p_vkDestroyImageView(device->device, hack->blit_view, NULL);
@@ -1837,6 +1836,7 @@ void WINAPI wine_vkDestroySwapchainKHR(VkDevice device, VkSwapchainKHR swapchain
             if(object->cmd_pools[i])
                 device->funcs.p_vkDestroyCommandPool(device->device, object->cmd_pools[i], NULL);
 
+        device->funcs.p_vkDestroyPipeline(device->device, object->pipeline, NULL);
         device->funcs.p_vkDestroyPipelineLayout(device->device, object->pipeline_layout, NULL);
         device->funcs.p_vkDestroyDescriptorSetLayout(device->device, object->descriptor_set_layout, NULL);
         device->funcs.p_vkDestroyDescriptorPool(device->device, object->descriptor_pool, NULL);
@@ -2023,6 +2023,22 @@ static VkResult init_blit_images(VkDevice device, struct VkSwapchainKHR_T *swapc
         goto fail;
     }
 
+    shaderInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
+    shaderInfo.codeSize = sizeof(blit_comp_spv);
+    shaderInfo.pCode = blit_comp_spv;
+
+    res = device->funcs.p_vkCreateShaderModule(device->device, &shaderInfo, NULL, &shaderModule);
+    if(res != VK_SUCCESS){
+        ERR("vkCreateShaderModule: %d\n", res);
+        goto fail;
+    }
+
+    res = create_pipeline(device, swapchain, shaderModule);
+    if(res != VK_SUCCESS)
+        goto fail;
+
+    device->funcs.p_vkDestroyShaderModule(device->device, shaderModule, NULL);
+
     if(!(swapchain->surface_usage & VK_IMAGE_USAGE_STORAGE_BIT)){
         TRACE("using intermediate blit images\n");
         /* create intermediate blit images */
@@ -2107,16 +2123,6 @@ static VkResult init_blit_images(VkDevice device, struct VkSwapchainKHR_T *swapc
     }else
         TRACE("blitting directly to swapchain images\n");
 
-    shaderInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
-    shaderInfo.codeSize = sizeof(blit_comp_spv);
-    shaderInfo.pCode = blit_comp_spv;
-
-    res = device->funcs.p_vkCreateShaderModule(device->device, &shaderInfo, NULL, &shaderModule);
-    if(res != VK_SUCCESS){
-        ERR("vkCreateShaderModule: %d\n", res);
-        goto fail;
-    }
-
     /* create imageviews */
     for(i = 0; i < swapchain->n_images; ++i){
         struct fs_hack_image *hack = &swapchain->fs_hack_images[i];
@@ -2140,23 +2146,14 @@ static VkResult init_blit_images(VkDevice device, struct VkSwapchainKHR_T *swapc
         res = create_descriptor_set(device, swapchain, hack);
         if(res != VK_SUCCESS)
             goto fail;
-
-        res = create_pipeline(device, swapchain, hack, shaderModule);
-        if(res != VK_SUCCESS)
-            goto fail;
     }
 
-    device->funcs.p_vkDestroyShaderModule(device->device, shaderModule, NULL);
-
     return VK_SUCCESS;
 
 fail:
     for(i = 0; i < swapchain->n_images; ++i){
         struct fs_hack_image *hack = &swapchain->fs_hack_images[i];
 
-        device->funcs.p_vkDestroyPipeline(device->device, hack->pipeline, NULL);
-        hack->pipeline = VK_NULL_HANDLE;
-
         device->funcs.p_vkFreeDescriptorSets(device->device, swapchain->descriptor_pool, 1, &hack->descriptor_set);
         hack->descriptor_set = VK_NULL_HANDLE;
 
@@ -2169,6 +2166,9 @@ static VkResult init_blit_images(VkDevice device, struct VkSwapchainKHR_T *swapc
 
     device->funcs.p_vkDestroyShaderModule(device->device, shaderModule, NULL);
 
+    device->funcs.p_vkDestroyPipeline(device->device, swapchain->pipeline, NULL);
+    swapchain->pipeline = VK_NULL_HANDLE;
+
     device->funcs.p_vkDestroyPipelineLayout(device->device, swapchain->pipeline_layout, NULL);
     swapchain->pipeline_layout = VK_NULL_HANDLE;
 
@@ -2258,7 +2258,7 @@ static VkResult record_compute_cmd(VkDevice device, struct VkSwapchainKHR_T *swa
 
     /* perform blit shader */
     device->funcs.p_vkCmdBindPipeline(hack->cmd,
-            VK_PIPELINE_BIND_POINT_COMPUTE, hack->pipeline);
+            VK_PIPELINE_BIND_POINT_COMPUTE, swapchain->pipeline);
 
     device->funcs.p_vkCmdBindDescriptorSets(hack->cmd,
             VK_PIPELINE_BIND_POINT_COMPUTE, swapchain->pipeline_layout,
diff --git a/dlls/winevulkan/vulkan_private.h b/dlls/winevulkan/vulkan_private.h
index 00626eff1e3..001ce85a1fd 100644
--- a/dlls/winevulkan/vulkan_private.h
+++ b/dlls/winevulkan/vulkan_private.h
@@ -147,7 +147,6 @@ struct fs_hack_image
     VkSemaphore blit_finished;
     VkImageView user_view, blit_view;
     VkDescriptorSet descriptor_set;
-    VkPipeline pipeline;
 };
 
 struct VkSwapchainKHR_T
@@ -170,6 +169,7 @@ struct VkSwapchainKHR_T
     VkDescriptorPool descriptor_pool;
     VkDescriptorSetLayout descriptor_set_layout;
     VkPipelineLayout pipeline_layout;
+    VkPipeline pipeline;
 };
 
 void *wine_vk_get_device_proc_addr(const char *name) DECLSPEC_HIDDEN;
From 76b525493429c6727f1875d556697ccf7b9fca08 Mon Sep 17 00:00:00 2001
From: Joshua Ashton <joshua@froggi.es>
Date: Mon, 24 Feb 2020 17:36:49 +0000
Subject: [PATCH] winevulkan: Cleanup barriers for fs hack

Removes needless GENERAL transitions for graphics presentation.
(we can be TRANSFER_SRC for vkCmdBlitImage, no need to disable DCC with GENERAL)
Access masks were also incorrect for this, this fixes those. (VK_ACCESS_TRANSFER_READ_BIT was missing)

Fixes some spec violations when presenting from compute queues:
Previously, we were transitioning to PRESENT_SRC too early and copies were happening to PRESENT_SRC which is illegal.
This patch will fix that by them being GENERAL (they are storage images anyway) at this point, then transitioning.
Also fixes misc. access flag issues.
---
 dlls/winevulkan/vulkan.c | 123 ++++++++++++++++++++-------------------
 1 file changed, 63 insertions(+), 60 deletions(-)

diff --git a/dlls/winevulkan/vulkan.c b/dlls/winevulkan/vulkan.c
index 8667dad9db4..d669b1a3c02 100644
--- a/dlls/winevulkan/vulkan.c
+++ b/dlls/winevulkan/vulkan.c
@@ -2216,9 +2216,10 @@ static VkResult record_compute_cmd(VkDevice device, struct VkSwapchainKHR_T *swa
 
     device->funcs.p_vkBeginCommandBuffer(hack->cmd, &beginInfo);
 
-    /* transition user image from GENERAL to SHADER_READ */
+    /* for the cs we run... */
+    /* transition user image from TRANSFER_SRC to SHADER_READ */
     barriers[0].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
-    barriers[0].oldLayout = VK_IMAGE_LAYOUT_GENERAL;
+    barriers[0].oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
     barriers[0].newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
     barriers[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
     barriers[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
@@ -2228,9 +2229,10 @@ static VkResult record_compute_cmd(VkDevice device, struct VkSwapchainKHR_T *swa
     barriers[0].subresourceRange.levelCount = 1;
     barriers[0].subresourceRange.baseArrayLayer = 0;
     barriers[0].subresourceRange.layerCount = 1;
-    barriers[0].srcAccessMask = 0;
+    barriers[0].srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
     barriers[0].dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
 
+    /* storage image... */
     /* transition blit image from whatever to GENERAL */
     barriers[1].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
     barriers[1].oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
@@ -2278,10 +2280,10 @@ static VkResult record_compute_cmd(VkDevice device, struct VkSwapchainKHR_T *swa
     device->funcs.p_vkCmdDispatch(hack->cmd, ceil(swapchain->real_extent.width / 8.),
             ceil(swapchain->real_extent.height / 8.), 1);
 
-    /* transition user image from SHADER_READ to GENERAL */
+    /* transition user image from SHADER_READ back to TRANSFER_SRC */
     barriers[0].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
     barriers[0].oldLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
-    barriers[0].newLayout = VK_IMAGE_LAYOUT_GENERAL;
+    barriers[0].newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
     barriers[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
     barriers[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
     barriers[0].image = hack->user_image;
@@ -2291,7 +2293,7 @@ static VkResult record_compute_cmd(VkDevice device, struct VkSwapchainKHR_T *swa
     barriers[0].subresourceRange.baseArrayLayer = 0;
     barriers[0].subresourceRange.layerCount = 1;
     barriers[0].srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
-    barriers[0].dstAccessMask = 0;
+    barriers[0].dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
 
     device->funcs.p_vkCmdPipelineBarrier(
             hack->cmd,
@@ -2304,11 +2306,11 @@ static VkResult record_compute_cmd(VkDevice device, struct VkSwapchainKHR_T *swa
     );
 
     if(hack->blit_image){
-        /* transition blit image layout from GENERAL to TRANSFER_SRC
-         * and access from SHADER_WRITE_BIT to TRANSFER_READ_BIT  */
+        /* for the copy... */
+        /* no transition, just a barrier for our access masks (w -> r) */
         barriers[0].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
         barriers[0].oldLayout = VK_IMAGE_LAYOUT_GENERAL;
-        barriers[0].newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
+        barriers[0].newLayout = VK_IMAGE_LAYOUT_GENERAL;
         barriers[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
         barriers[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
         barriers[0].image = hack->blit_image;
@@ -2320,10 +2322,12 @@ static VkResult record_compute_cmd(VkDevice device, struct VkSwapchainKHR_T *swa
         barriers[0].srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT;
         barriers[0].dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
 
-        /* transition swapchain image from whatever to PRESENT_SRC */
+        /* for the copy... */
+        /* transition swapchain image from whatever to TRANSFER_DST
+         * we don't care about the contents... */
         barriers[1].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
         barriers[1].oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
-        barriers[1].newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
+        barriers[1].newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
         barriers[1].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
         barriers[1].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
         barriers[1].image = hack->swapchain_image;
@@ -2361,9 +2365,34 @@ static VkResult record_compute_cmd(VkDevice device, struct VkSwapchainKHR_T *swa
         region.extent.depth = 1;
 
         device->funcs.p_vkCmdCopyImage(hack->cmd,
-                hack->blit_image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
-                hack->swapchain_image, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
+                hack->blit_image, VK_IMAGE_LAYOUT_GENERAL,
+                hack->swapchain_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
                 1, &region);
+
+        /* transition swapchain image from TRANSFER_DST_OPTIMAL to PRESENT_SRC */
+        barriers[0].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+        barriers[0].oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
+        barriers[0].newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
+        barriers[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+        barriers[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+        barriers[0].image = hack->swapchain_image;
+        barriers[0].subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+        barriers[0].subresourceRange.baseMipLevel = 0;
+        barriers[0].subresourceRange.levelCount = 1;
+        barriers[0].subresourceRange.baseArrayLayer = 0;
+        barriers[0].subresourceRange.layerCount = 1;
+        barriers[0].srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+        barriers[0].dstAccessMask = 0;
+
+        device->funcs.p_vkCmdPipelineBarrier(
+                hack->cmd,
+                VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
+                VK_PIPELINE_STAGE_TRANSFER_BIT,
+                0,
+                0, NULL,
+                0, NULL,
+                1, barriers
+        );
     }else{
         /* transition swapchain image from GENERAL to PRESENT_SRC */
         barriers[0].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
@@ -2421,35 +2450,20 @@ static VkResult record_graphics_cmd(VkDevice device, struct VkSwapchainKHR_T *sw
 
     device->funcs.p_vkBeginCommandBuffer(hack->cmd, &beginInfo);
 
-    /* transition user image from GENERAL to TRANSFER_SRC_OPTIMAL */
+    /* transition real image from whatever to TRANSFER_DST_OPTIMAL */
     barriers[0].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
-    barriers[0].oldLayout = VK_IMAGE_LAYOUT_GENERAL;
-    barriers[0].newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
+    barriers[0].oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+    barriers[0].newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
     barriers[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
     barriers[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
-    barriers[0].image = hack->user_image;
+    barriers[0].image = hack->swapchain_image;
     barriers[0].subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
     barriers[0].subresourceRange.baseMipLevel = 0;
     barriers[0].subresourceRange.levelCount = 1;
     barriers[0].subresourceRange.baseArrayLayer = 0;
     barriers[0].subresourceRange.layerCount = 1;
     barriers[0].srcAccessMask = 0;
-    barriers[0].dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
-
-    /* transition real image from whatever to TRANSFER_DST_OPTIMAL */
-    barriers[1].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
-    barriers[1].oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
-    barriers[1].newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
-    barriers[1].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
-    barriers[1].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
-    barriers[1].image = hack->swapchain_image;
-    barriers[1].subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
-    barriers[1].subresourceRange.baseMipLevel = 0;
-    barriers[1].subresourceRange.levelCount = 1;
-    barriers[1].subresourceRange.baseArrayLayer = 0;
-    barriers[1].subresourceRange.layerCount = 1;
-    barriers[1].srcAccessMask = 0;
-    barriers[1].dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+    barriers[0].dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
 
     device->funcs.p_vkCmdPipelineBarrier(
             hack->cmd,
@@ -2458,7 +2472,7 @@ static VkResult record_graphics_cmd(VkDevice device, struct VkSwapchainKHR_T *sw
             0,
             0, NULL,
             0, NULL,
-            2, barriers
+            1, barriers
     );
 
     /* clear the image */
@@ -2496,36 +2510,21 @@ static VkResult record_graphics_cmd(VkDevice device, struct VkSwapchainKHR_T *sw
             hack->swapchain_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
             1, &blitregion, swapchain->fs_hack_filter);
 
-    /* transition user image from TRANSFER_SRC_OPTIMAL to GENERAL */
+    /* transition real image from TRANSFER_DST to PRESENT_SRC */
     barriers[0].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
-    barriers[0].oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
-    barriers[0].newLayout = VK_IMAGE_LAYOUT_GENERAL;
+    barriers[0].oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
+    barriers[0].newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
     barriers[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
     barriers[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
-    barriers[0].image = hack->user_image;
+    barriers[0].image = hack->swapchain_image;
     barriers[0].subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
     barriers[0].subresourceRange.baseMipLevel = 0;
     barriers[0].subresourceRange.levelCount = 1;
     barriers[0].subresourceRange.baseArrayLayer = 0;
     barriers[0].subresourceRange.layerCount = 1;
-    barriers[0].srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
+    barriers[0].srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
     barriers[0].dstAccessMask = 0;
 
-    /* transition real image from TRANSFER_DST to PRESENT_SRC */
-    barriers[1].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
-    barriers[1].oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
-    barriers[1].newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
-    barriers[1].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
-    barriers[1].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
-    barriers[1].image = hack->swapchain_image;
-    barriers[1].subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
-    barriers[1].subresourceRange.baseMipLevel = 0;
-    barriers[1].subresourceRange.levelCount = 1;
-    barriers[1].subresourceRange.baseArrayLayer = 0;
-    barriers[1].subresourceRange.layerCount = 1;
-    barriers[1].srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
-    barriers[1].dstAccessMask = 0;
-
     device->funcs.p_vkCmdPipelineBarrier(
             hack->cmd,
             VK_PIPELINE_STAGE_TRANSFER_BIT,
@@ -2533,7 +2532,7 @@ static VkResult record_graphics_cmd(VkDevice device, struct VkSwapchainKHR_T *sw
             0,
             0, NULL,
             0, NULL,
-            2, barriers
+            1, barriers
     );
 
     result = device->funcs.p_vkEndCommandBuffer(hack->cmd);
@@ -2687,7 +2686,7 @@ void WINAPI wine_vkCmdPipelineBarrier(VkCommandBuffer commandBuffer,
 #endif
 
     /* if the client is trying to transition a user image to PRESENT_SRC,
-     * transition it to GENERAL instead. */
+     * transition it to TRANSFER_SRC_OPTIMAL instead. */
     EnterCriticalSection(&commandBuffer->device->swapchain_lock);
     for(i = 0; i < imageMemoryBarrierCount; ++i){
         old = pImageMemoryBarriers[i].oldLayout == VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
@@ -2703,10 +2702,14 @@ void WINAPI wine_vkCmdPipelineBarrier(VkCommandBuffer commandBuffer,
                             if(!pImageMemoryBarriers_host)
                                 pImageMemoryBarriers_host = convert_VkImageMemoryBarrier_array_win_to_host(pImageMemoryBarriers, imageMemoryBarrierCount);
 #endif
-                            if(old)
-                                pImageMemoryBarriers_host[i].oldLayout = VK_IMAGE_LAYOUT_GENERAL;
-                            if(new)
-                                pImageMemoryBarriers_host[i].newLayout = VK_IMAGE_LAYOUT_GENERAL;
+                            if(old) {
+                                pImageMemoryBarriers_host[i].oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
+                                pImageMemoryBarriers_host[i].srcAccessMask |= VK_ACCESS_TRANSFER_READ_BIT;
+                            }
+                            if(new) {
+                                pImageMemoryBarriers_host[i].newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
+                                pImageMemoryBarriers_host[i].dstAccessMask |= VK_ACCESS_TRANSFER_READ_BIT;
+                            }
                             goto next;
                         }
                     }
From 136bf78a8fc5fd8f8841b8409233d0579373c1fb Mon Sep 17 00:00:00 2001
From: Joshua Ashton <joshua@froggi.es>
Date: Tue, 25 Feb 2020 19:03:31 +0000
Subject: [PATCH] winevulkan: Fix warning due to implicit different ptr size
 cast

---
 dlls/winevulkan/vulkan.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/dlls/winevulkan/vulkan.c b/dlls/winevulkan/vulkan.c
index d669b1a3c02..b11c0a81fc4 100644
--- a/dlls/winevulkan/vulkan.c
+++ b/dlls/winevulkan/vulkan.c
@@ -1773,7 +1773,7 @@ VkResult WINAPI wine_vkCreateSwapchainKHR(VkDevice device, const VkSwapchainCrea
         result = init_blit_images(device, object);
         if(result != VK_SUCCESS){
             ERR("creating blit images failed: %d\n", result);
-            wine_vkDestroySwapchainKHR(device, (VkSwapchainKHR)object, NULL);
+            wine_vkDestroySwapchainKHR(device, (VkSwapchainKHR)(UINT_PTR)object, NULL);
             return result;
         }
     }
From 37320358dec2b1cbff3f8f1554b8d73dfa9fd33a Mon Sep 17 00:00:00 2001
From: Georg Lehmann <dadschoorse@gmail.com>
Date: Mon, 2 Mar 2020 14:53:35 +0100
Subject: [PATCH] winevulkan: fshack: don't change the transitions to PRESENT

We can't be sure that the application transitions with barriers and not with render passes.
So we let it transition to PRESENT and change our layout changes accordingly.

Also fixes that the user image is created without VK_IMAGE_USAGE_TRANSFER_SRC_BIT.
---
 dlls/winevulkan/vulkan.c | 85 ++++++++++++++++++----------------------
 1 file changed, 39 insertions(+), 46 deletions(-)

diff --git a/dlls/winevulkan/vulkan.c b/dlls/winevulkan/vulkan.c
index b11c0a81fc4..028eb99f7e2 100644
--- a/dlls/winevulkan/vulkan.c
+++ b/dlls/winevulkan/vulkan.c
@@ -1589,7 +1589,7 @@ static VkResult init_fs_hack_images(VkDevice device, struct VkSwapchainKHR_T *sw
         imageInfo.format = createinfo->imageFormat;
         imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
         imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
-        imageInfo.usage = createinfo->imageUsage | VK_IMAGE_USAGE_SAMPLED_BIT;
+        imageInfo.usage = createinfo->imageUsage | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
         imageInfo.sharingMode = createinfo->imageSharingMode;
         imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
         imageInfo.queueFamilyIndexCount = createinfo->queueFamilyIndexCount;
@@ -2217,9 +2217,9 @@ static VkResult record_compute_cmd(VkDevice device, struct VkSwapchainKHR_T *swa
     device->funcs.p_vkBeginCommandBuffer(hack->cmd, &beginInfo);
 
     /* for the cs we run... */
-    /* transition user image from TRANSFER_SRC to SHADER_READ */
+    /* transition user image from PRESENT_SRC to SHADER_READ */
     barriers[0].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
-    barriers[0].oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
+    barriers[0].oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
     barriers[0].newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
     barriers[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
     barriers[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
@@ -2229,7 +2229,7 @@ static VkResult record_compute_cmd(VkDevice device, struct VkSwapchainKHR_T *swa
     barriers[0].subresourceRange.levelCount = 1;
     barriers[0].subresourceRange.baseArrayLayer = 0;
     barriers[0].subresourceRange.layerCount = 1;
-    barriers[0].srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
+    barriers[0].srcAccessMask = 0;
     barriers[0].dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
 
     /* storage image... */
@@ -2280,10 +2280,10 @@ static VkResult record_compute_cmd(VkDevice device, struct VkSwapchainKHR_T *swa
     device->funcs.p_vkCmdDispatch(hack->cmd, ceil(swapchain->real_extent.width / 8.),
             ceil(swapchain->real_extent.height / 8.), 1);
 
-    /* transition user image from SHADER_READ back to TRANSFER_SRC */
+    /* transition user image from SHADER_READ back to PRESENT_SRC */
     barriers[0].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
     barriers[0].oldLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
-    barriers[0].newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
+    barriers[0].newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
     barriers[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
     barriers[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
     barriers[0].image = hack->user_image;
@@ -2293,7 +2293,7 @@ static VkResult record_compute_cmd(VkDevice device, struct VkSwapchainKHR_T *swa
     barriers[0].subresourceRange.baseArrayLayer = 0;
     barriers[0].subresourceRange.layerCount = 1;
     barriers[0].srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
-    barriers[0].dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
+    barriers[0].dstAccessMask = 0;
 
     device->funcs.p_vkCmdPipelineBarrier(
             hack->cmd,
@@ -2465,6 +2465,21 @@ static VkResult record_graphics_cmd(VkDevice device, struct VkSwapchainKHR_T *sw
     barriers[0].srcAccessMask = 0;
     barriers[0].dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
 
+    /* transition user image from PRESENT_SRC to TRANSFER_SRC_OPTIMAL */
+    barriers[1].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+    barriers[1].oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
+    barriers[1].newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
+    barriers[1].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+    barriers[1].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+    barriers[1].image = hack->user_image;
+    barriers[1].subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+    barriers[1].subresourceRange.baseMipLevel = 0;
+    barriers[1].subresourceRange.levelCount = 1;
+    barriers[1].subresourceRange.baseArrayLayer = 0;
+    barriers[1].subresourceRange.layerCount = 1;
+    barriers[1].srcAccessMask = 0;
+    barriers[1].dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
+
     device->funcs.p_vkCmdPipelineBarrier(
             hack->cmd,
             VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
@@ -2472,7 +2487,7 @@ static VkResult record_graphics_cmd(VkDevice device, struct VkSwapchainKHR_T *sw
             0,
             0, NULL,
             0, NULL,
-            1, barriers
+            2, barriers
     );
 
     /* clear the image */
@@ -2525,6 +2540,21 @@ static VkResult record_graphics_cmd(VkDevice device, struct VkSwapchainKHR_T *sw
     barriers[0].srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
     barriers[0].dstAccessMask = 0;
 
+    /* transition user image from TRANSFER_SRC_OPTIMAL to back to PRESENT_SRC */
+    barriers[1].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+    barriers[1].oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
+    barriers[1].newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
+    barriers[1].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+    barriers[1].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+    barriers[1].image = hack->user_image;
+    barriers[1].subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+    barriers[1].subresourceRange.baseMipLevel = 0;
+    barriers[1].subresourceRange.levelCount = 1;
+    barriers[1].subresourceRange.baseArrayLayer = 0;
+    barriers[1].subresourceRange.layerCount = 1;
+    barriers[1].srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
+    barriers[1].dstAccessMask = 0;
+
     device->funcs.p_vkCmdPipelineBarrier(
             hack->cmd,
             VK_PIPELINE_STAGE_TRANSFER_BIT,
@@ -2532,7 +2562,7 @@ static VkResult record_graphics_cmd(VkDevice device, struct VkSwapchainKHR_T *sw
             0,
             0, NULL,
             0, NULL,
-            1, barriers
+            2, barriers
     );
 
     result = device->funcs.p_vkEndCommandBuffer(hack->cmd);
@@ -2675,8 +2705,6 @@ void WINAPI wine_vkCmdPipelineBarrier(VkCommandBuffer commandBuffer,
     VkBufferMemoryBarrier_host *pBufferMemoryBarriers_host;
 #endif
     VkImageMemoryBarrier_host *pImageMemoryBarriers_host = NULL;
-    uint32_t i, j, k;
-    int old, new;
 
     TRACE("%p, %#x, %#x, %#x, %u, %p, %u, %p, %u, %p\n", commandBuffer, srcStageMask, dstStageMask, dependencyFlags, memoryBarrierCount, pMemoryBarriers, bufferMemoryBarrierCount, pBufferMemoryBarriers, imageMemoryBarrierCount, pImageMemoryBarriers);
 
@@ -2685,41 +2713,6 @@ void WINAPI wine_vkCmdPipelineBarrier(VkCommandBuffer commandBuffer,
     pImageMemoryBarriers_host = convert_VkImageMemoryBarrier_array_win_to_host(pImageMemoryBarriers, imageMemoryBarrierCount);
 #endif
 
-    /* if the client is trying to transition a user image to PRESENT_SRC,
-     * transition it to TRANSFER_SRC_OPTIMAL instead. */
-    EnterCriticalSection(&commandBuffer->device->swapchain_lock);
-    for(i = 0; i < imageMemoryBarrierCount; ++i){
-        old = pImageMemoryBarriers[i].oldLayout == VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
-        new = pImageMemoryBarriers[i].newLayout == VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
-        if(old || new){
-            for(j = 0; j < commandBuffer->device->num_swapchains; ++j){
-                struct VkSwapchainKHR_T *swapchain = commandBuffer->device->swapchains[j];
-                if(swapchain->fs_hack_enabled){
-                    for(k = 0; k < swapchain->n_images; ++k){
-                        struct fs_hack_image *hack = &swapchain->fs_hack_images[k];
-                        if(pImageMemoryBarriers[i].image == hack->user_image){
-#if !defined(USE_STRUCT_CONVERSION)
-                            if(!pImageMemoryBarriers_host)
-                                pImageMemoryBarriers_host = convert_VkImageMemoryBarrier_array_win_to_host(pImageMemoryBarriers, imageMemoryBarrierCount);
-#endif
-                            if(old) {
-                                pImageMemoryBarriers_host[i].oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
-                                pImageMemoryBarriers_host[i].srcAccessMask |= VK_ACCESS_TRANSFER_READ_BIT;
-                            }
-                            if(new) {
-                                pImageMemoryBarriers_host[i].newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
-                                pImageMemoryBarriers_host[i].dstAccessMask |= VK_ACCESS_TRANSFER_READ_BIT;
-                            }
-                            goto next;
-                        }
-                    }
-                }
-            }
-        }
-next:   ;
-    }
-    LeaveCriticalSection(&commandBuffer->device->swapchain_lock);
-
     commandBuffer->device->funcs.p_vkCmdPipelineBarrier(commandBuffer->command_buffer,
             srcStageMask, dstStageMask, dependencyFlags, memoryBarrierCount,
             pMemoryBarriers, bufferMemoryBarrierCount,
From a060b973af303139dceb6d89f71f24f22fae9197 Mon Sep 17 00:00:00 2001
From: Georg Lehmann <dadschoorse@gmail.com>
Date: Mon, 2 Mar 2020 18:07:15 +0100
Subject: [PATCH] winevulkan: fshack: don't free invidual descriptor set

They get destroyed when the pool gets destroyed anyway.
---
 dlls/winevulkan/vulkan.c | 1 -
 1 file changed, 1 deletion(-)

diff --git a/dlls/winevulkan/vulkan.c b/dlls/winevulkan/vulkan.c
index 028eb99f7e2..bad31300df8 100644
--- a/dlls/winevulkan/vulkan.c
+++ b/dlls/winevulkan/vulkan.c
@@ -1509,7 +1509,6 @@ static VkResult create_descriptor_set(VkDevice device, struct VkSwapchainKHR_T *
 
 static void destroy_fs_hack_image(VkDevice device, struct VkSwapchainKHR_T *swapchain, struct fs_hack_image *hack)
 {
-    device->funcs.p_vkFreeDescriptorSets(device->device, swapchain->descriptor_pool, 1, &hack->descriptor_set);
     device->funcs.p_vkDestroyImageView(device->device, hack->user_view, NULL);
     device->funcs.p_vkDestroyImageView(device->device, hack->blit_view, NULL);
     device->funcs.p_vkDestroyImage(device->device, hack->user_image, NULL);
From a4f4dd8c6578bb153a06487d071cbd790a83aa3a Mon Sep 17 00:00:00 2001
From: Georg Lehmann <dadschoorse@gmail.com>
Date: Wed, 4 Mar 2020 13:50:12 +0100
Subject: [PATCH] winevulkan: generate wine_vkCmdPipelineBarrier again

since we removed everything special from that function, we can generate it again
---
 dlls/winevulkan/make_vulkan     |  1 -
 dlls/winevulkan/vulkan.c        | 38 ---------------------------------
 dlls/winevulkan/vulkan_thunks.c | 19 +++++++++++++++++
 dlls/winevulkan/vulkan_thunks.h |  1 -
 4 files changed, 19 insertions(+), 40 deletions(-)

diff --git a/dlls/winevulkan/make_vulkan b/dlls/winevulkan/make_vulkan
index 10728785223..b58ca9301cc 100755
--- a/dlls/winevulkan/make_vulkan
+++ b/dlls/winevulkan/make_vulkan
@@ -171,7 +171,6 @@ FUNCTION_OVERRIDES = {
     "vkCmdExecuteCommands" : {"dispatch" : True, "driver" : False, "thunk" : False},
     "vkCreateCommandPool" : {"dispatch": True, "driver" : False, "thunk" : False},
     "vkDestroyCommandPool" : {"dispatch": True, "driver" : False, "thunk" : False},
-    "vkCmdPipelineBarrier" : {"dispatch" : True, "driver" : False, "thunk" : False},
     "vkDestroyDevice" : {"dispatch" : True, "driver" : False, "thunk" : False},
     "vkFreeCommandBuffers" : {"dispatch" : True, "driver" : False, "thunk" : False},
     "vkGetDeviceProcAddr" : {"dispatch" : False, "driver" : True, "thunk" : False},
diff --git a/dlls/winevulkan/vulkan.c b/dlls/winevulkan/vulkan.c
index bad31300df8..e2991c2dc47 100644
--- a/dlls/winevulkan/vulkan.c
+++ b/dlls/winevulkan/vulkan.c
@@ -2693,44 +2693,6 @@ VkResult WINAPI wine_vkQueuePresentKHR(VkQueue queue, const VkPresentInfoKHR *pP
 
 }
 
-void WINAPI wine_vkCmdPipelineBarrier(VkCommandBuffer commandBuffer,
-        VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask,
-        VkDependencyFlags dependencyFlags, uint32_t memoryBarrierCount,
-        const VkMemoryBarrier *pMemoryBarriers, uint32_t bufferMemoryBarrierCount,
-        const VkBufferMemoryBarrier *pBufferMemoryBarriers, uint32_t imageMemoryBarrierCount,
-        const VkImageMemoryBarrier *pImageMemoryBarriers)
-{
-#if defined(USE_STRUCT_CONVERSION)
-    VkBufferMemoryBarrier_host *pBufferMemoryBarriers_host;
-#endif
-    VkImageMemoryBarrier_host *pImageMemoryBarriers_host = NULL;
-
-    TRACE("%p, %#x, %#x, %#x, %u, %p, %u, %p, %u, %p\n", commandBuffer, srcStageMask, dstStageMask, dependencyFlags, memoryBarrierCount, pMemoryBarriers, bufferMemoryBarrierCount, pBufferMemoryBarriers, imageMemoryBarrierCount, pImageMemoryBarriers);
-
-#if defined(USE_STRUCT_CONVERSION)
-    pBufferMemoryBarriers_host = convert_VkBufferMemoryBarrier_array_win_to_host(pBufferMemoryBarriers, bufferMemoryBarrierCount);
-    pImageMemoryBarriers_host = convert_VkImageMemoryBarrier_array_win_to_host(pImageMemoryBarriers, imageMemoryBarrierCount);
-#endif
-
-    commandBuffer->device->funcs.p_vkCmdPipelineBarrier(commandBuffer->command_buffer,
-            srcStageMask, dstStageMask, dependencyFlags, memoryBarrierCount,
-            pMemoryBarriers, bufferMemoryBarrierCount,
-#if defined(USE_STRUCT_CONVERSION)
-            pBufferMemoryBarriers_host, imageMemoryBarrierCount, pImageMemoryBarriers_host
-#else
-            pBufferMemoryBarriers, imageMemoryBarrierCount,
-            pImageMemoryBarriers_host ? (VkImageMemoryBarrier*)pImageMemoryBarriers_host : pImageMemoryBarriers
-#endif
-            );
-
-#if defined(USE_STRUCT_CONVERSION)
-    free_VkBufferMemoryBarrier_array(pBufferMemoryBarriers_host, bufferMemoryBarrierCount);
-#else
-    if(pImageMemoryBarriers_host)
-#endif
-        free_VkImageMemoryBarrier_array(pImageMemoryBarriers_host, imageMemoryBarrierCount);
-}
-
 VkDevice WINAPI __wine_get_native_VkDevice(VkDevice device)
 {
     return device->device;
diff --git a/dlls/winevulkan/vulkan_thunks.c b/dlls/winevulkan/vulkan_thunks.c
index f073539bd53..ea9e8262d83 100644
--- a/dlls/winevulkan/vulkan_thunks.c
+++ b/dlls/winevulkan/vulkan_thunks.c
@@ -2895,6 +2895,25 @@ static void WINAPI wine_vkCmdNextSubpass2KHR(VkCommandBuffer commandBuffer, cons
     commandBuffer->device->funcs.p_vkCmdNextSubpass2KHR(commandBuffer->command_buffer, pSubpassBeginInfo, pSubpassEndInfo);
 }
 
+void WINAPI wine_vkCmdPipelineBarrier(VkCommandBuffer commandBuffer, VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask, VkDependencyFlags dependencyFlags, uint32_t memoryBarrierCount, const VkMemoryBarrier *pMemoryBarriers, uint32_t bufferMemoryBarrierCount, const VkBufferMemoryBarrier *pBufferMemoryBarriers, uint32_t imageMemoryBarrierCount, const VkImageMemoryBarrier *pImageMemoryBarriers)
+{
+#if defined(USE_STRUCT_CONVERSION)
+    VkBufferMemoryBarrier_host *pBufferMemoryBarriers_host;
+    VkImageMemoryBarrier_host *pImageMemoryBarriers_host;
+    TRACE("%p, %#x, %#x, %#x, %u, %p, %u, %p, %u, %p\n", commandBuffer, srcStageMask, dstStageMask, dependencyFlags, memoryBarrierCount, pMemoryBarriers, bufferMemoryBarrierCount, pBufferMemoryBarriers, imageMemoryBarrierCount, pImageMemoryBarriers);
+
+    pBufferMemoryBarriers_host = convert_VkBufferMemoryBarrier_array_win_to_host(pBufferMemoryBarriers, bufferMemoryBarrierCount);
+    pImageMemoryBarriers_host = convert_VkImageMemoryBarrier_array_win_to_host(pImageMemoryBarriers, imageMemoryBarrierCount);
+    commandBuffer->device->funcs.p_vkCmdPipelineBarrier(commandBuffer->command_buffer, srcStageMask, dstStageMask, dependencyFlags, memoryBarrierCount, pMemoryBarriers, bufferMemoryBarrierCount, pBufferMemoryBarriers_host, imageMemoryBarrierCount, pImageMemoryBarriers_host);
+
+    free_VkBufferMemoryBarrier_array(pBufferMemoryBarriers_host, bufferMemoryBarrierCount);
+    free_VkImageMemoryBarrier_array(pImageMemoryBarriers_host, imageMemoryBarrierCount);
+#else
+    TRACE("%p, %#x, %#x, %#x, %u, %p, %u, %p, %u, %p\n", commandBuffer, srcStageMask, dstStageMask, dependencyFlags, memoryBarrierCount, pMemoryBarriers, bufferMemoryBarrierCount, pBufferMemoryBarriers, imageMemoryBarrierCount, pImageMemoryBarriers);
+    commandBuffer->device->funcs.p_vkCmdPipelineBarrier(commandBuffer->command_buffer, srcStageMask, dstStageMask, dependencyFlags, memoryBarrierCount, pMemoryBarriers, bufferMemoryBarrierCount, pBufferMemoryBarriers, imageMemoryBarrierCount, pImageMemoryBarriers);
+#endif
+}
+
 void WINAPI wine_vkCmdPushConstants(VkCommandBuffer commandBuffer, VkPipelineLayout layout, VkShaderStageFlags stageFlags, uint32_t offset, uint32_t size, const void *pValues)
 {
     TRACE("%p, 0x%s, %#x, %u, %u, %p\n", commandBuffer, wine_dbgstr_longlong(layout), stageFlags, offset, size, pValues);
diff --git a/dlls/winevulkan/vulkan_thunks.h b/dlls/winevulkan/vulkan_thunks.h
index 08ede0bd2af..82d0c3a0a5e 100644
--- a/dlls/winevulkan/vulkan_thunks.h
+++ b/dlls/winevulkan/vulkan_thunks.h
@@ -44,7 +44,6 @@
 VkResult WINAPI wine_vkAcquireNextImageKHR(VkDevice device, VkSwapchainKHR swapchain, uint64_t timeout, VkSemaphore semaphore, VkFence fence, uint32_t *pImageIndex);
 VkResult WINAPI wine_vkAllocateCommandBuffers(VkDevice device, const VkCommandBufferAllocateInfo *pAllocateInfo, VkCommandBuffer *pCommandBuffers);
 void WINAPI wine_vkCmdExecuteCommands(VkCommandBuffer commandBuffer, uint32_t commandBufferCount, const VkCommandBuffer *pCommandBuffers);
-void WINAPI wine_vkCmdPipelineBarrier(VkCommandBuffer commandBuffer, VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask, VkDependencyFlags dependencyFlags, uint32_t memoryBarrierCount, const VkMemoryBarrier *pMemoryBarriers, uint32_t bufferMemoryBarrierCount, const VkBufferMemoryBarrier *pBufferMemoryBarriers, uint32_t imageMemoryBarrierCount, const VkImageMemoryBarrier *pImageMemoryBarriers);
 VkResult WINAPI wine_vkCreateCommandPool(VkDevice device, const VkCommandPoolCreateInfo *pCreateInfo, const VkAllocationCallbacks *pAllocator, VkCommandPool *pCommandPool);
 VkResult WINAPI wine_vkCreateDevice(VkPhysicalDevice physicalDevice, const VkDeviceCreateInfo *pCreateInfo, const VkAllocationCallbacks *pAllocator, VkDevice *pDevice);
 VkResult WINAPI wine_vkCreateSwapchainKHR(VkDevice device, const VkSwapchainCreateInfoKHR *pCreateInfo, const VkAllocationCallbacks *pAllocator, VkSwapchainKHR *pSwapchain);

From 71fce64369f2ace00bc0bb8018b549dd201a5764 Mon Sep 17 00:00:00 2001
From: Nikolay Sivov <nsivov@codeweavers.com>
Date: Wed, 9 Jan 2019 13:24:54 +0300
Subject: [PATCH] imm32: Automatically initialize COM on window activation.

Signed-off-by: Nikolay Sivov <nsivov@codeweavers.com>
---
 dlls/imm32/Makefile.in     |   2 +-
 dlls/imm32/imm.c           | 129 ++++++++++++++++++++++++++++++++++++-
 dlls/imm32/imm32.spec      |   1 +
 dlls/user32/focus.c        |   2 +
 dlls/user32/misc.c         |   2 +
 dlls/user32/user_private.h |   1 +
 6 files changed, 135 insertions(+), 2 deletions(-)

diff --git a/dlls/imm32/Makefile.in b/dlls/imm32/Makefile.in
index b190888659b..ad10fc2fa45 100644
--- a/dlls/imm32/Makefile.in
+++ b/dlls/imm32/Makefile.in
@@ -1,6 +1,6 @@
 MODULE    = imm32.dll
 IMPORTLIB = imm32
-IMPORTS   = user32 gdi32 advapi32
+IMPORTS   = user32 gdi32 advapi32 ole32
 
 C_SRCS = \
 	imm.c
diff --git a/dlls/imm32/imm.c b/dlls/imm32/imm.c
index 28eb00f355a..129f7e8cb53 100644
--- a/dlls/imm32/imm.c
+++ b/dlls/imm32/imm.c
@@ -19,6 +19,8 @@
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
  */
 
+#define COBJMACROS
+
 #include <stdarg.h>
 #include <stdio.h>
 
@@ -32,6 +34,8 @@
 #include "ddk/imm.h"
 #include "winnls.h"
 #include "winreg.h"
+#include "initguid.h"
+#include "objbase.h"
 #include "wine/list.h"
 #include "wine/unicode.h"
 
@@ -95,8 +99,16 @@ typedef struct _tagIMMThreadData {
     HWND hwndDefault;
     BOOL disableIME;
     DWORD windowRefs;
+    IInitializeSpy IInitializeSpy_iface;
+    ULARGE_INTEGER spy_cookie;
+    BOOL apt_initialized;
 } IMMThreadData;
 
+static inline IMMThreadData *impl_from_IInitializeSpy(IInitializeSpy *iface)
+{
+    return CONTAINING_RECORD(iface, IMMThreadData, IInitializeSpy_iface);
+}
+
 static struct list ImmHklList = LIST_INIT(ImmHklList);
 static struct list ImmThreadDataList = LIST_INIT(ImmThreadDataList);
 
@@ -227,6 +239,88 @@ static DWORD convert_candidatelist_AtoW(
     return ret;
 }
 
+static HRESULT WINAPI initializespy_QueryInterface(IInitializeSpy *iface, REFIID riid, void **obj)
+{
+    if (IsEqualIID(&IID_IInitializeSpy, riid) ||
+            IsEqualIID(&IID_IUnknown, riid))
+    {
+        *obj = iface;
+        IInitializeSpy_AddRef(iface);
+        return S_OK;
+    }
+
+    *obj = NULL;
+    return E_NOINTERFACE;
+}
+
+static ULONG WINAPI initializespy_AddRef(IInitializeSpy *iface)
+{
+    return 2;
+}
+
+static ULONG WINAPI initializespy_Release(IInitializeSpy *iface)
+{
+    return 1;
+}
+
+static void imm_couninit_thread(IMMThreadData *thread_data)
+{
+    if (!thread_data->apt_initialized)
+        return;
+
+    thread_data->apt_initialized = FALSE;
+    CoUninitialize();
+}
+
+static HRESULT WINAPI initializespy_PreInitialize(IInitializeSpy *iface, DWORD coinit, DWORD refs)
+{
+    IMMThreadData *thread_data = impl_from_IInitializeSpy(iface);
+
+    /* Application requested initialization of different apartment type. */
+    if (!(coinit & COINIT_APARTMENTTHREADED))
+        imm_couninit_thread(thread_data);
+
+    return S_OK;
+}
+
+static HRESULT WINAPI initializespy_PostInitialize(IInitializeSpy *iface, HRESULT hr, DWORD coinit, DWORD refs)
+{
+    IMMThreadData *thread_data = impl_from_IInitializeSpy(iface);
+
+    /* Explicit initialization call should return S_OK first time. */
+    if (thread_data->apt_initialized && hr == S_FALSE && refs == 2)
+        hr = S_OK;
+
+    return hr;
+}
+
+static HRESULT WINAPI initializespy_PreUninitialize(IInitializeSpy *iface, DWORD refs)
+{
+    IMMThreadData *thread_data = impl_from_IInitializeSpy(iface);
+
+    /* Account for explicit uninitialization calls. */
+    if (thread_data->apt_initialized && refs == 1)
+        thread_data->apt_initialized = FALSE;
+
+    return S_OK;
+}
+
+static HRESULT WINAPI initializespy_PostUninitialize(IInitializeSpy *iface, DWORD refs)
+{
+    return S_OK;
+}
+
+static const IInitializeSpyVtbl initializespyvtbl =
+{
+    initializespy_QueryInterface,
+    initializespy_AddRef,
+    initializespy_Release,
+    initializespy_PreInitialize,
+    initializespy_PostInitialize,
+    initializespy_PreUninitialize,
+    initializespy_PostUninitialize,
+};
+
 static IMMThreadData *IMM_GetThreadData(HWND hwnd, DWORD thread)
 {
     IMMThreadData *data;
@@ -253,6 +347,7 @@ static IMMThreadData *IMM_GetThreadData(HWND hwnd, DWORD thread)
         if (data->threadID == thread) return data;
 
     data = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*data));
+    data->IInitializeSpy_iface.lpVtbl = &initializespyvtbl;
     data->threadID = thread;
     list_add_head(&ImmThreadDataList,&data->entry);
     TRACE("Thread Data Created (%x)\n",thread);
@@ -281,6 +376,7 @@ static void IMM_FreeThreadData(void)
             list_remove(&data->entry);
             LeaveCriticalSection(&threaddata_cs);
             IMM_DestroyContext(data->defaultContext);
+            imm_couninit_thread(data);
             HeapFree(GetProcessHeap(),0,data);
             TRACE("Thread Data Destroyed\n");
             return;
@@ -1627,6 +1723,32 @@ static BOOL needs_ime_window(HWND hwnd)
     return TRUE;
 }
 
+void WINAPI __wine_activate_window(HWND hwnd)
+{
+    IMMThreadData *thread_data;
+
+    TRACE("(%p)\n", hwnd);
+
+    if (!needs_ime_window(hwnd))
+        return;
+
+    thread_data = IMM_GetThreadData(hwnd, 0);
+    if (!thread_data)
+        return;
+
+    if (thread_data->disableIME || disable_ime)
+    {
+        TRACE("IME for this thread is disabled\n");
+        LeaveCriticalSection(&threaddata_cs);
+        return;
+    }
+
+    if (!thread_data->apt_initialized)
+        thread_data->apt_initialized = SUCCEEDED(CoInitializeEx(NULL, COINIT_APARTMENTTHREADED));
+
+    LeaveCriticalSection(&threaddata_cs);
+}
+
 /***********************************************************************
  *		__wine_register_window (IMM32.@)
  */
@@ -1656,6 +1778,8 @@ BOOL WINAPI __wine_register_window(HWND hwnd)
     /* Create default IME window */
     if (thread_data->windowRefs == 1)
     {
+        CoRegisterInitializeSpy(&thread_data->IInitializeSpy_iface, &thread_data->spy_cookie);
+
         /* Do not create the window inside of a critical section */
         LeaveCriticalSection(&threaddata_cs);
         new = CreateWindowExW( 0, szwIME, szwDefaultIME,
@@ -1697,8 +1821,11 @@ void WINAPI __wine_unregister_window(HWND hwnd)
           thread_data->windowRefs, thread_data->hwndDefault);
 
     /* Destroy default IME window */
-    if (thread_data->windowRefs == 0 && thread_data->hwndDefault)
+    if (thread_data->windowRefs == 0)
     {
+        CoRevokeInitializeSpy(thread_data->spy_cookie);
+        thread_data->spy_cookie.QuadPart = 0;
+        imm_couninit_thread(thread_data);
         to_destroy = thread_data->hwndDefault;
         thread_data->hwndDefault = NULL;
     }
diff --git a/dlls/imm32/imm32.spec b/dlls/imm32/imm32.spec
index 4197bb81e21..d9cdc794e9e 100644
--- a/dlls/imm32/imm32.spec
+++ b/dlls/imm32/imm32.spec
@@ -117,3 +117,4 @@
 @ stdcall __wine_get_ui_window(ptr)
 @ stdcall __wine_register_window(long)
 @ stdcall __wine_unregister_window(long)
+@ stdcall __wine_activate_window(long)
diff --git a/dlls/user32/focus.c b/dlls/user32/focus.c
index f1c883167ed..50b3323ae9f 100644
--- a/dlls/user32/focus.c
+++ b/dlls/user32/focus.c
@@ -156,6 +156,8 @@ static BOOL set_active_window( HWND hwnd, HWND *prev, BOOL mouse, BOOL focus )
                       (LPARAM)previous );
         if (GetAncestor( hwnd, GA_PARENT ) == GetDesktopWindow())
             PostMessageW( GetDesktopWindow(), WM_PARENTNOTIFY, WM_NCACTIVATE, (LPARAM)hwnd );
+
+        imm_activate_window( hwnd );
     }
 
     /* now change focus if necessary */
diff --git a/dlls/user32/misc.c b/dlls/user32/misc.c
index c26c02a3537..5cbcc7db67d 100644
--- a/dlls/user32/misc.c
+++ b/dlls/user32/misc.c
@@ -43,6 +43,7 @@ WINE_DEFAULT_DEBUG_CHANNEL(win);
 static HWND (WINAPI *imm_get_ui_window)(HKL);
 BOOL (WINAPI *imm_register_window)(HWND) = NULL;
 void (WINAPI *imm_unregister_window)(HWND) = NULL;
+void (WINAPI *imm_activate_window)(HWND) = NULL;
 
 /* MSIME messages */
 static UINT WM_MSIME_SERVICE;
@@ -567,6 +568,7 @@ BOOL WINAPI User32InitializeImmEntryTable(DWORD magic)
     imm_get_ui_window = (void*)GetProcAddress(imm32, "__wine_get_ui_window");
     imm_register_window = (void*)GetProcAddress(imm32, "__wine_register_window");
     imm_unregister_window = (void*)GetProcAddress(imm32, "__wine_unregister_window");
+    imm_activate_window = (void*)GetProcAddress(imm32, "__wine_activate_window");
     if (!imm_get_ui_window)
         FIXME("native imm32.dll not supported\n");
     return TRUE;
diff --git a/dlls/user32/user_private.h b/dlls/user32/user_private.h
index 514cf6753f4..b86831d7d95 100644
--- a/dlls/user32/user_private.h
+++ b/dlls/user32/user_private.h
@@ -197,6 +197,7 @@ C_ASSERT( sizeof(struct user_thread_info) <= sizeof(((TEB *)0)->Win32ClientInfo)
 extern INT global_key_state_counter DECLSPEC_HIDDEN;
 extern BOOL (WINAPI *imm_register_window)(HWND) DECLSPEC_HIDDEN;
 extern void (WINAPI *imm_unregister_window)(HWND) DECLSPEC_HIDDEN;
+extern void (WINAPI *imm_activate_window)(HWND) DECLSPEC_HIDDEN;
 
 struct user_key_state_info
 {
From 501b068024375386484c6a8616a406b21ab15406 Mon Sep 17 00:00:00 2001
From: Andrew Eikum <aeikum@codeweavers.com>
Date: Thu, 30 Jan 2020 14:44:00 -0600
Subject: [PATCH] user32: HACK: Don't resize new windows to fit the display

---
 dlls/user32/win.c | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/dlls/user32/win.c b/dlls/user32/win.c
index fa593a4a29a..803259b351f 100644
--- a/dlls/user32/win.c
+++ b/dlls/user32/win.c
@@ -1607,12 +1607,17 @@ HWND WIN_CreateWindowEx( CREATESTRUCTW *cs, LPCWSTR className, HINSTANCE module,
 
     cx = cs->cx;
     cy = cs->cy;
+#if 0
+    /* HACK: This code changes the window's size to fit the display. However,
+     * some games (Bayonetta, Dragon's Dogma) will then have the incorrect
+     * render size. So just let windows be too big to fit the display. */
     if ((cs->style & WS_THICKFRAME) || !(cs->style & (WS_POPUP | WS_CHILD)))
     {
         MINMAXINFO info = WINPOS_GetMinMaxInfo( hwnd );
         cx = max( min( cx, info.ptMaxTrackSize.x ), info.ptMinTrackSize.x );
         cy = max( min( cy, info.ptMaxTrackSize.y ), info.ptMinTrackSize.y );
     }
+#endif
 
     if (cx < 0) cx = 0;
     if (cy < 0) cy = 0;
From f53d131b499a303ae3f68d20265117b84b175802 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?R=C3=A9mi=20Bernon?= <rbernon@codeweavers.com>
Date: Tue, 10 Mar 2020 20:47:33 +0100
Subject: [PATCH] fixup! user32: HACK: Don't resize new windows to fit the
 display

This code changes the window's size to fit the display. However,
some games (Bayonetta, Dragon's Dogma) will then have the incorrect
render size. So just let windows be too big to fit the display.
---
 dlls/user32/win.c    | 9 ++-------
 dlls/user32/winpos.c | 2 --
 2 files changed, 2 insertions(+), 9 deletions(-)

diff --git a/dlls/user32/win.c b/dlls/user32/win.c
index 803259b351f..8a04f611ce5 100644
--- a/dlls/user32/win.c
+++ b/dlls/user32/win.c
@@ -1607,17 +1607,12 @@ HWND WIN_CreateWindowEx( CREATESTRUCTW *cs, LPCWSTR className, HINSTANCE module,
 
     cx = cs->cx;
     cy = cs->cy;
-#if 0
-    /* HACK: This code changes the window's size to fit the display. However,
-     * some games (Bayonetta, Dragon's Dogma) will then have the incorrect
-     * render size. So just let windows be too big to fit the display. */
     if ((cs->style & WS_THICKFRAME) || !(cs->style & (WS_POPUP | WS_CHILD)))
     {
         MINMAXINFO info = WINPOS_GetMinMaxInfo( hwnd );
-        cx = max( min( cx, info.ptMaxTrackSize.x ), info.ptMinTrackSize.x );
-        cy = max( min( cy, info.ptMaxTrackSize.y ), info.ptMinTrackSize.y );
+        cx = max( cx, info.ptMinTrackSize.x );
+        cy = max( cy, info.ptMinTrackSize.y );
     }
-#endif
 
     if (cx < 0) cx = 0;
     if (cy < 0) cy = 0;
diff --git a/dlls/user32/winpos.c b/dlls/user32/winpos.c
index f759bd10f94..7974060acf0 100644
--- a/dlls/user32/winpos.c
+++ b/dlls/user32/winpos.c
@@ -1589,8 +1589,6 @@ LONG WINPOS_HandleWindowPosChanging( HWND hwnd, WINDOWPOS *winpos )
     if ((style & WS_THICKFRAME) || ((style & (WS_POPUP | WS_CHILD)) == 0))
     {
 	MINMAXINFO info = WINPOS_GetMinMaxInfo( hwnd );
-        winpos->cx = min( winpos->cx, info.ptMaxTrackSize.x );
-        winpos->cy = min( winpos->cy, info.ptMaxTrackSize.y );
 	if (!(style & WS_MINIMIZE))
 	{
             winpos->cx = max( winpos->cx, info.ptMinTrackSize.x );
From 6c6117644e303b376f9a3717e858e76d75532ae5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?R=C3=A9mi=20Bernon?= <rbernon@codeweavers.com>
Date: Thu, 12 Mar 2020 15:53:07 +0100
Subject: [PATCH] HACK: user32: Export window manager info from user driver.

---
 dlls/user32/win.c         | 10 ++++++++++
 dlls/user32/winpos.c      | 10 ++++++++++
 dlls/winex11.drv/window.c | 24 ++++++++++++------------
 include/wine/gdi_driver.h | 18 ++++++++++++++++++
 4 files changed, 50 insertions(+), 12 deletions(-)

diff --git a/dlls/user32/win.c b/dlls/user32/win.c
index 8a04f611ce5..aca0a347844 100644
--- a/dlls/user32/win.c
+++ b/dlls/user32/win.c
@@ -1610,6 +1610,16 @@ HWND WIN_CreateWindowEx( CREATESTRUCTW *cs, LPCWSTR className, HINSTANCE module,
     if ((cs->style & WS_THICKFRAME) || !(cs->style & (WS_POPUP | WS_CHILD)))
     {
         MINMAXINFO info = WINPOS_GetMinMaxInfo( hwnd );
+
+        /* HACK: This code changes the window's size to fit the display. However,
+         * some games (Bayonetta, Dragon's Dogma) will then have the incorrect
+         * render size. So just let windows be too big to fit the display. */
+        if (__wine_get_window_manager() != WINE_WM_X11_STEAMCOMPMGR)
+        {
+            cx = min( cx, info.ptMaxTrackSize.x );
+            cy = min( cy, info.ptMaxTrackSize.y );
+        }
+
         cx = max( cx, info.ptMinTrackSize.x );
         cy = max( cy, info.ptMinTrackSize.y );
     }
diff --git a/dlls/user32/winpos.c b/dlls/user32/winpos.c
index 7974060acf0..f917146d91c 100644
--- a/dlls/user32/winpos.c
+++ b/dlls/user32/winpos.c
@@ -1589,6 +1589,16 @@ LONG WINPOS_HandleWindowPosChanging( HWND hwnd, WINDOWPOS *winpos )
     if ((style & WS_THICKFRAME) || ((style & (WS_POPUP | WS_CHILD)) == 0))
     {
 	MINMAXINFO info = WINPOS_GetMinMaxInfo( hwnd );
+
+        /* HACK: This code changes the window's size to fit the display. However,
+         * some games (Bayonetta, Dragon's Dogma) will then have the incorrect
+         * render size. So just let windows be too big to fit the display. */
+        if (__wine_get_window_manager() != WINE_WM_X11_STEAMCOMPMGR)
+        {
+            winpos->cx = min( winpos->cx, info.ptMaxTrackSize.x );
+            winpos->cy = min( winpos->cy, info.ptMaxTrackSize.y );
+        }
+
 	if (!(style & WS_MINIMIZE))
 	{
             winpos->cx = max( winpos->cx, info.ptMinTrackSize.x );
diff --git a/dlls/winex11.drv/window.c b/dlls/winex11.drv/window.c
index b2700164a3d..47cbced56e2 100644
--- a/dlls/winex11.drv/window.c
+++ b/dlls/winex11.drv/window.c
@@ -102,10 +102,6 @@ static CRITICAL_SECTION_DEBUG critsect_debug =
 };
 static CRITICAL_SECTION win_data_section = { &critsect_debug, -1, 0, 0, 0, 0 };
 
-static const int WM_UNKNOWN = 0;
-static const int WM_MUTTER = 1;
-static const int WM_STEAMCOMPMGR = 2;
-
 /* enable workarounds for mutter bugs */
 static int detect_wm(Display *dpy)
 {
@@ -142,23 +138,25 @@ static int detect_wm(Display *dpy)
 
                     if((strcmp(wm_name, "GNOME Shell") == 0) ||
                             (strcmp(wm_name, "Mutter") == 0))
-                        cached = WM_MUTTER;
+                        cached = WINE_WM_X11_MUTTER;
                     else if(strcmp(wm_name, "steamcompmgr") == 0)
-                        cached = WM_STEAMCOMPMGR;
+                        cached = WINE_WM_X11_STEAMCOMPMGR;
                     else
-                        cached = WM_UNKNOWN;
+                        cached = WINE_WM_UNKNOWN;
 
                     XFree(wm_name);
                 }else{
                     TRACE("WM did not set _NET_WM_NAME or WM_NAME\n");
-                    cached = WM_UNKNOWN;
+                    cached = WINE_WM_UNKNOWN;
                 }
             }else
-                cached = WM_UNKNOWN;
+                cached = WINE_WM_UNKNOWN;
 
             XFree(wm_check);
         }else
-            cached = WM_UNKNOWN;
+            cached = WINE_WM_UNKNOWN;
+
+        __wine_set_window_manager(cached);
     }
 
     return cached;
@@ -166,12 +164,12 @@ static int detect_wm(Display *dpy)
 
 BOOL wm_is_mutter(Display *display)
 {
-    return detect_wm(display) == WM_MUTTER;
+    return detect_wm(display) == WINE_WM_X11_MUTTER;
 }
 
 BOOL wm_is_steamcompmgr(Display *display)
 {
-    return detect_wm(display) == WM_STEAMCOMPMGR;
+    return detect_wm(display) == WINE_WM_X11_STEAMCOMPMGR;
 }
 
 /***********************************************************************
@@ -1959,6 +1957,8 @@ BOOL CDECL X11DRV_CreateDesktopWindow( HWND hwnd )
 {
     unsigned int width, height;
 
+    detect_wm( gdi_display );
+
     /* retrieve the real size of the desktop */
     SERVER_START_REQ( get_window_rectangles )
     {
diff --git a/include/wine/gdi_driver.h b/include/wine/gdi_driver.h
index 971618e19ac..aa8052f1844 100644
--- a/include/wine/gdi_driver.h
+++ b/include/wine/gdi_driver.h
@@ -22,6 +22,7 @@
 #define __WINE_WINE_GDI_DRIVER_H
 
 #include "winternl.h"
+#include "winuser.h"
 #include "ddk/d3dkmthk.h"
 #include "wine/list.h"
 
@@ -289,4 +290,21 @@ extern void CDECL __wine_set_display_driver( HMODULE module );
 extern struct opengl_funcs * CDECL __wine_get_wgl_driver( HDC hdc, UINT version );
 extern const struct vulkan_funcs * CDECL __wine_get_vulkan_driver( HDC hdc, UINT version );
 
+/* HACK: We use some WM specific hacks in user32 and we need the user
+ * driver to export that information. */
+
+#define WINE_WM_UNKNOWN          0
+#define WINE_WM_X11_MUTTER       1
+#define WINE_WM_X11_STEAMCOMPMGR 2
+
+static inline LONG_PTR __wine_get_window_manager(void)
+{
+    return (LONG_PTR)GetPropA(GetDesktopWindow(), "__wine_window_manager");
+}
+
+static inline void __wine_set_window_manager(LONG_PTR window_manager)
+{
+    SetPropA(GetDesktopWindow(), "__wine_window_manager", (HANDLE)window_manager);
+}
+
 #endif /* __WINE_WINE_GDI_DRIVER_H */

From 2f5e0756621332a39d7119553a69cc26f77dfc9b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?R=C3=A9mi=20Bernon?= <rbernon@codeweavers.com>
Date: Mon, 16 Mar 2020 19:52:52 +0100
Subject: [PATCH] HACK: user: Pretend that windows are all undecorated with
 gamescope.

---
 dlls/user32/nonclient.c | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/dlls/user32/nonclient.c b/dlls/user32/nonclient.c
index 0aee13787af..c9156e73578 100644
--- a/dlls/user32/nonclient.c
+++ b/dlls/user32/nonclient.c
@@ -30,6 +30,7 @@
 #include "user_private.h"
 #include "controls.h"
 #include "wine/debug.h"
+#include "wine/gdi_driver.h"
 
 WINE_DEFAULT_DEBUG_CHANNEL(nonclient);
 
@@ -62,6 +63,9 @@ static void adjust_window_rect( RECT *rect, DWORD style, BOOL menu, DWORD exStyl
 {
     int adjust = 0;
 
+    if (__wine_get_window_manager() == WINE_WM_X11_STEAMCOMPMGR)
+        return;
+
     if ((exStyle & (WS_EX_STATICEDGE|WS_EX_DLGMODALFRAME)) == WS_EX_STATICEDGE)
         adjust = 1; /* for the outer frame always present */
     else if ((exStyle & WS_EX_DLGMODALFRAME) || (style & (WS_THICKFRAME|WS_DLGFRAME)))
@@ -359,6 +363,9 @@ LRESULT NC_HandleNCCalcSize( HWND hwnd, WPARAM wparam, RECT *winRect )
     if (winRect == NULL)
         return 0;
 
+    if (__wine_get_window_manager() == WINE_WM_X11_STEAMCOMPMGR)
+        return 0;
+
     if (cls_style & CS_VREDRAW) result |= WVR_VREDRAW;
     if (cls_style & CS_HREDRAW) result |= WVR_HREDRAW;
 
